You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In interactive mode with the claude-code-cli provider, async_bash job completion notifications delivered via deliverAs:"followUp" are treated as the user's next instruction. The agent autonomously acts on the job output without any user prompt — in this case, immediately launching into code fixes the user had not yet approved, and visually pushing the prior response out of the window.
Root Cause
Two defects combine to produce the failure:
Defect 1 — extractLastUserPrompt in stream-adapter.ts ingests followUp messages as user prompts
When pi.sendMessage({deliverAs:"followUp"}) appends an async_job_result to pi's context, it lands as a user-role message. extractLastUserPrompt iterates backwards through context.messages and returns the first role:"user" message — which is now the job result. That string ("**Background job done: bg_e7c23baa**…\n\n=== live play ===\n\"started\"\n…") becomes the Claude Code SDK's prompt. The SDK runs a new full turn, and the agent acts on the job output as if the user requested it.
// Current — no guard for followUp/custom messagesfunctionextractLastUserPrompt(context: Context): string{for(leti=context.messages.length-1;i>=0;i--){constmsg=context.messages[i];if(msg.role==="user"){// Returns job result content as the SDK prompt ← BUGif(typeofmsg.content==="string")returnmsg.content;
...
}}return"";}
Fix: Skip messages with customType === "async_job_result" (or any message flagged as a followUp) when walking backwards:
functionextractLastUserPrompt(context: Context): string{for(leti=context.messages.length-1;i>=0;i--){constmsg=context.messages[i];if(msg.role==="user"){// Skip follow-up notifications — informational only, not user instructionsif((msgasany).customType==="async_job_result")continue;if((msgasany).isFollowUp===true)continue;if(typeofmsg.content==="string")returnmsg.content;
...
}}return"";}
Defect 2 — No interactive-mode equivalent of clearQueue() for claude-code-cli sessions
Auto-mode calls clearQueue() after each unit completes to discard pending followUp messages. Interactive mode has no such call. With native pi providers this is tolerable because deliverAs:"followUp" does not trigger a new turn when idle. With the claude-code-cli provider the followUp becomes the prompt on the next turn (Defect 1), so there is no safe idle state — every job completion that finishes after the agent's response will cause an autonomous turn.
Expected Behavior
async_job_result followUp messages should display in the TUI but never become the Claude Code SDK prompt.
The agent should remain idle after delivering a response; job completions should not trigger autonomous follow-up turns in interactive mode.
In an interactive session with the claude-code-cli provider, launch 4+ parallel async_bash jobs.
Agent produces a response (turn ends).
Background jobs complete → onJobComplete fires → pi.sendMessage({deliverAs:"followUp"}) queues results.
FollowUp messages appear in TUI, pushing prior response out of view.
On the next conversation cycle, extractLastUserPrompt returns the last job result string as the user's instruction.
Claude Code SDK runs a new turn treating job output as a user prompt.
Agent acts autonomously on the results (in this case, immediately began implementing code fixes before the user had reviewed the audit findings).
Forensic Evidence
4 async_bash jobs started during an audit (bg_fb7d16ae, bg_e7c23baa, bg_d3fe95de, bg_ee6d8891).
Agent gave a complete audit response, then job results arrived one by one.
Each result (**Background job done: bg_e7c23baa** (test live viewport controls, 1.5s)\n\n=== live play ===\n"started"\n…) appeared as a new user-turn prompt.
Agent acted on results without user instruction, launching into code fixes.
Problem
In interactive mode with the claude-code-cli provider,
async_bashjob completion notifications delivered viadeliverAs:"followUp"are treated as the user's next instruction. The agent autonomously acts on the job output without any user prompt — in this case, immediately launching into code fixes the user had not yet approved, and visually pushing the prior response out of the window.Root Cause
Two defects combine to produce the failure:
Defect 1 —
extractLastUserPromptinstream-adapter.tsingests followUp messages as user promptsFile:
src/resources/extensions/claude-code-cli/stream-adapter.ts(~line 80,extractLastUserPrompt)When
pi.sendMessage({deliverAs:"followUp"})appends anasync_job_resultto pi's context, it lands as a user-role message.extractLastUserPromptiterates backwards throughcontext.messagesand returns the firstrole:"user"message — which is now the job result. That string ("**Background job done: bg_e7c23baa**…\n\n=== live play ===\n\"started\"\n…") becomes the Claude Code SDK's prompt. The SDK runs a new full turn, and the agent acts on the job output as if the user requested it.Fix: Skip messages with
customType === "async_job_result"(or any message flagged as a followUp) when walking backwards:Defect 2 — No interactive-mode equivalent of
clearQueue()for claude-code-cli sessionsFile:
src/resources/extensions/gsd/auto/run-unit.ts(lines ~121–133)Auto-mode calls
clearQueue()after each unit completes to discard pending followUp messages. Interactive mode has no such call. With native pi providers this is tolerable becausedeliverAs:"followUp"does not trigger a new turn when idle. With the claude-code-cli provider the followUp becomes the prompt on the next turn (Defect 1), so there is no safe idle state — every job completion that finishes after the agent's response will cause an autonomous turn.Expected Behavior
async_job_resultfollowUp messages should display in the TUI but never become the Claude Code SDK prompt.Environment
@anthropic-ai/claude-agent-sdk)Reproduction Context
async_bashjobs.onJobCompletefires →pi.sendMessage({deliverAs:"followUp"})queues results.extractLastUserPromptreturns the last job result string as the user's instruction.Forensic Evidence
async_bashjobs started during an audit (bg_fb7d16ae,bg_e7c23baa,bg_d3fe95de,bg_ee6d8891).**Background job done: bg_e7c23baa** (test live viewport controls, 1.5s)\n\n=== live play ===\n"started"\n…) appeared as a new user-turn prompt.Prior Art
triggerTurn:true→deliverAs:"followUp"for native providers. Does not cover the claude-code-cli path where followUps become SDK prompts viaextractLastUserPrompt.clearQueue()inrun-unit.ts. Interactive mode has no equivalent.Auto-generated by
/gsd forensics