Repository navigation
[Bug]: Pi turn-start prompt lacks streamingBehavior — pi 1.0.x rejects prompts while an agent run is active #15221
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks @DerAndereAndi for the RPC capture and for pointing straight at the steer path. I confirmed this on current
main(ce90eec1) and on the nightly in your report (f391794a35c6). The turn fails because the Pi turn-start prompt doesn't setstreamingBehaviorwhile Pi is already streaming. This is separate from issue #13507 and open PR #13839, which cover extension rewinds and ref reconciliation.What I found
startTurnsends a bareprompt:t3code/apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts
Lines 2358 to 2362 in ce90eec
yield* connection.send({ type: "prompt", message: payload.message, ...(payload.images.length === 0 ? {} : { images: payload.images }), }); steerTurnalready passesstreamingBehavior: "steer". In Pi, that option queues the message whileisStreamingis set and is ignored when Pi is idle, in which case a new run starts.t3code/apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts
Lines 2421 to 2446 in ce90eec
// Prompt with streamingBehavior steer is atomic on Pi's side: it // queues during an active run and starts a new run if settlement // won the race. A direct `steer` sent after Pi became idle would // remain queued forever. Send fire-and-forget under the session // permit so a slash-command dialog cannot block the turn, and so // settlement cannot overtake the active-turn check. // /compact is not a prompt: Pi's compact RPC aborts the agent first. yield* sessionEventPermit.withPermits(1)( Effect.gen(function* () { if (threadState?.activeTurn !== turn) { return yield* protocolError(`Pi turn ${steerInput.providerTurnId} is not active`); } if (compactCommand !== null) { turn.manualCompactInFlight = true; yield* connection.send(compactRpcRecord(compactCommand)); pendingCompactResponses.push({ providerTurnId: turn.providerTurn.id, kind: "steer", }); } else if (payload !== null) { yield* connection.send({ type: "prompt", message: payload.message, streamingBehavior: "steer", ...(payload.images.length === 0 ? {} : { images: payload.images }), }); One small correction to the version framing: the throw itself predates Pi 1.0.
AgentSession.prompt()has rejected a prompt withoutstreamingBehaviorduring streaming since at least Pi 0.80.5, T3's minimum (source). Pi 1.0.x just makes it easy to hit, because afterswitch_sessionan extension continuation can already be streaming by the time T3 prompts.registerThreaddoes callget_statebut never readsisStreaming.A rejected turn-start prompt is stored as a provider failure and the turn is finalized, which is the banner you saw. That path leaves the Pi process running. The
ProviderAdapterEventStreamError/pi process exited with code 0trace comes from the stdout-close path, used when Pi exits on its own. It's worth keeping on the report, but the bare prompt is what blocks the thread.Likely fix area
A maintainer will decide on the fix direction. The option that matches the existing steer path is to send the turn-start prompt with
streamingBehavior: "steer": an idle session still starts a run, and a streaming one gets the user message injected instead of a failed turn.followUpwould wait behind the extension run, and abort-then-prompt can race extensions that resume themselves (abortalso doesn't cancel compaction). The message-steering replay fixture currently expects the initial prompt to omitstreamingBehavior, so that expectation would need to change too.Workaround and side note
--no-extensionsin the Pi provider's launch arguments is a valid workaround. T3 appends its own--extensionafter those arguments, so the T3 bridge stays loaded while discovery extensions don't.The bare
1.0.1line on stdout is separate and harmless: non-JSON stdout lines are dropped and don't break the RPC stream.- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 3, 2026 - added 6 commits that reference this issue
on Oct 6, 2026
Area: apps/server
Steps to reproduce
npm i -g @earendil-works/pi-coding-agentor bun global) and enable the Pi provider.switch_session.)T3's turn-start prompt races the extension-driven run:
Expected behavior
The turn starts. Since pi 1.0.x,
AgentSession.prompt()rejects a barepromptwhile a run is active —agent-session.ts:1965-1970at tag v1.0.1:So the turn-start prompt must either pass
streamingBehavior(pi's steer semantics queue it atomically during an active run) or T3 should checkget_stateand abort the active run before prompting.Actual behavior
The prompt is rejected and the user sees:
The turn never starts; the session is left in an error state. Every retry while an extension-driven run is in flight bounces the same way, so the thread is effectively unusable.
Impact
Blocks work completely (for the affected thread). Workaround exists but is costly — see below.
Version or commit
0.0.46-nightly.20261003.2632(f391794). Defect present at currentmain(ce90eec): turn-start sends a bare prompt atapps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts:2358-2362while the steer path (line 2444) already passesstreamingBehavior: "steer"— its own comment (line 2421) documents pi's queue-or-run semantics:Environment
macOS 27.0.1 (arm64), T3 Code desktop (Nightly), pi
@earendil-works/pi-coding-agent1.0.1 (bun global install; behavior identical on 1.0.0), Node v26.9.0.Related
#13507 / #13839 cover extension-driven session rewinds and their ref/rollback reconciliation — a different mechanism. This report is about the prompt contract itself: pi 1.0.x made bare
promptthrow while a run is active, and T3's turn-start path (tested against pi 0.8x, cf. #13839's verification against pi 0.87.1) hasn't caught up. Both are part of the same theme: extensions drive pi sessions in ways the adapter's request paths don't fully anticipate.Logs or stack traces
RPC capture (tee on pi stdin/stdout) of the rejected exchange and the extension-driven runs that follow — full transcript available on request. Redacted: session paths truncated; no secrets in the capture.
Workaround
Setting the Pi provider's Launch arguments to
--no-extensionsstops extension-discovery extensions (pi-lens, pi-loop, …) from loading in T3 sessions — T3's own--extensionstill loads — which removes the busy-at-load races. Cost: pi-lens features are unavailable in T3 sessions. Alternatively wait for extension-driven runs to settle before prompting.Minor side note
A bare
1.0.1(version string) line appears on pi's stdout in RPC mode — most likely pi's own version/update-check output landing on the protocol channel. T3 drops non-JSON lines so it's harmless, but raw prints on the RPC channel would be worth avoiding.