Note
Grok responding on behalf of Julius.
Triage
Thanks for the scripted repro, the control run, and the protocol log, @nekohasekai. They made this straightforward to trace.
What I found
This only happens after Claude emits task_started for a foreground local_bash call. A steer sent before that point aborts the call and is read in the same process. A steer sent after it waits in Claude's in-memory queue until the tool returns. If Stop is pressed during that wait, the CLI process exits with the steer still in its queue.
dispatchSteerIntoRuninOrchestrator.tswrites the steer into the active run as a completed user message. The timeline shows "Steered the active turn" as soon as the offer is accepted, before Claude has read it.ClaudeAdapterV2.steerTurnoffers the text withpriority: "now"and nouuid. Prompts fromstartTurngetclaudePromptUuid(attemptId), and the adapter already readscommand_lifecyclefor that uuid. A steer has no uuid, so the adapter can't tell a queued steer from one Claude has started.- Stop (
run.interrupt,holdQueue: true) callsquery.interruptand thenquery.close, which ends the process.holdQueueonly holds later queued runs and doesn't cover a steer already attached to the running run. - The resumed turn offers only the new prompt. Nothing returns the unread steer to the composer or the queue, and the bubble still shows it as steered.
#13423 dealt with the reverse case, a steer still running after Stop. It was closed during the V2 migration and doesn't cover this case.
Likely fix area
Some options:
- Give steers a
uuidsocommand_lifecyclecan show whether Claude actually started reading one before Stop. - On Stop, mark an unread steer as not delivered, or return it to the composer or the queue.
- Alternatively, send the unread steer again on the next turn.
A maintainer will decide on the fix direction.