Repository navigation
[Bug]: Pi turn-start prompt lacks streamingBehavior — pi 1.0.x rejects prompts while an agent run is active #15221
Description
Area: apps/server
Steps to reproduce
- Install pi ≥ 1.0.0 (observed on 1.0.0 and 1.0.1;
npm i -g @earendil-works/pi-coding-agentor bun global) and enable the Pi provider. - Have a pi session whose extension state triggers a run on resume — e.g. a pi-loop extension iteration waiting on a monitor event, or any pending extension continuation. (Observed: session last ended mid-run, so the resumed session re-fires work immediately after
switch_session.) - Open a T3 thread bound to that session and send a message.
T3's turn-start prompt races the extension-driven run:
→ {"type":"switch_session","sessionPath":"…/session.jsonl","id":"t3-0"}
→ {"type":"get_commands","id":"t3-1"}
→ {"type":"get_state","id":"t3-2"}
→ {"type":"get_available_models","id":"t3-3"}
→ {"type":"get_entries","id":"t3-4"}
→ {"type":"set_model",…,"id":"t3-5"}
→ {"type":"set_session_name",…,"id":"t3-6"}
→ {"type":"prompt","message":"Context handoff (delta_since_target_last_seen):…"}
← {"type":"response","command":"prompt","success":false,"error":"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message."}
Expected behavior
The turn starts. Since pi 1.0.x, AgentSession.prompt() rejects a bare prompt while a run is active — agent-session.ts:1965-1970 at tag v1.0.1:
// If streaming, queue via steer() or followUp() based on option
if (this.isStreaming) {
if (!options?.streamingBehavior) {
throw new Error(
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
);
}So the turn-start prompt must either pass streamingBehavior (pi's steer semantics queue it atomically during an active run) or T3 should check get_state and abort the active run before prompting.
Actual behavior
The prompt is rejected and the user sees:
Provider error: Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.
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 current main (ce90eec): turn-start sends a bare prompt at apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts:2358-2362 while the steer path (line 2444) already passes streamingBehavior: "steer" — its own comment (line 2421) documents pi's queue-or-run semantics:
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.
Environment
macOS 27.0.1 (arm64), T3 Code desktop (Nightly), pi @earendil-works/pi-coding-agent 1.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 prompt throw 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
ProviderAdapterEventStreamError: Failed while streaming pi provider session provider-session:provider-instance:pi:thread:…:… events.
at orchestrationV2.providerTurnStart.start (apps/server/dist/binCli-D9mHsJrM.mjs:228797)
[cause]: Error: Cause([Fail(PiRpcError: Pi RPC read failed: pi process exited with code 0.)])
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-extensions stops extension-discovery extensions (pi-lens, pi-loop, …) from loading in T3 sessions — T3's own --extension still 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.
Activity
juliusmarminge commentedon Oct 3, 2026
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
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
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.fix(server): Pi turns start while an extension run is streaming
fix(server): Pi turns start while an extension run is streaming
fix(server): Pi turns start while an extension run is streaming
fix(server): Pi turns start while an extension run is streaming
fix(server): Pi turns start while an extension run is streaming
fix(server): Pi turns start while an extension run is streaming