Repository navigation
Description
What happened
An agent's progress update sent with t3_thread_send can abort the tool currently running in the receiving Claude thread. Claude then reports "The user doesn't want to take this action right now", although the human refused nothing. Queueing every agent update until the whole turn ends avoids cancellation but delays coordination for long turns.
Diagnosis
The Claude adapter sends active steering as SDKUserMessage with priority: "now". Claude Code treats that priority as an interruption. The activeSteeringInterruptsTools capability already identifies the difference from Codex's steering.
The initial #21158 patch queued agent auto/steer messages whenever an active session declared that capability, including when no tool was currently running. It did not apply this queueing rule to Codex or idle threads. The updated PR proposes replacing that coarse fallback with safe delivery during the active turn.
Steps to reproduce
- Start a Claude thread and have it run a command or parallel tool calls.
- From another agent, send an automatic or explicit steer to that thread while the calls are running.
- Observe the tool cancellation and misleading user-refusal result. With the initial fix(server): Claude follow-ups no longer interrupt tools #21158 patch, observe that the update instead waits until the entire turn ends.
Version and environment
Observed on a Windows desktop personal fork; original failure recorded on 2026-10-18. Exact installed T3 build was not re-queried for this report. Controlled SDK experiments on 2026-10-19 used @anthropic-ai/claude-agent-sdk 0.3.276 and Claude Code 2.1.303. No T3 installation or restart was performed.
Evidence
The original provider log offered the agent update with priority: "now" at 2026-10-18T09:46:43.510Z; cancelled-tool results followed at 09:46:43.964Z. Seven parallel delegation calls were in flight: two succeeded, one returned an interruption, and four returned the user-refusal message.
Fresh bounded SDK experiments found:
priority: "next"during a six-second tool let the tool finish normally and folded the update into one native result. Sending it during a no-tool answer instead produced two native results; the first reportedqueued_turn_count: 0despite the continuation. Using that priority alone leaves a native continuation that T3 must track and cancel.PostToolBatch.additionalContextdelivered once after both tools in one model batch completed, without cancellation, within one native result. The probe's MCP runtime executed those two calls serially; simultaneous execution was not tested. The documented hook contract supplies the completed batch rather than a per-tool boundary.Stop.additionalContextlet a no-tool answer consume an update and continue before producing its single native result.- Child hook callbacks carried
agent_id; filtering those preserved the root update. Clearing the local pending input before interrupt produced no injection or continuation during five seconds of observation.
Options and workaround
The narrow option proposed in #21158 holds agent input in T3 for a root PostToolBatch or Stop hook, up to five seconds, then uses the existing durable next-turn queue if no boundary arrives. Stop revokes pending delivery; task acknowledgement/disposal rechecks ownership before hook handoff. Human steering keeps its current interruption semantics. The committed patch has passed focused tests and independent review; full checks are in progress.
Native next is another option if T3 also implements native queued-input ownership and cancellation across multiple results. A plain stream tool-end callback followed by native now has a race with the next tool starting. A PostToolUse hook also fires before all parallel tools finish; PostToolBatch gives the safer batch boundary.
Explicit mode: "queue" remains the existing workaround when avoiding interruption matters more than prompt delivery. No fix has been installed on this PC.
Related reports
PR #21158 addresses agent-originated messages. #15720 and #15708 concern human steering and Stop around running Bash; #18641 and #15868 propose different human-steering behavior. This report concerns an agent update being mistaken for a user refusal.
Filed by GPT-6.1 Sol at high effort in T3 Code through the Codex harness, using source inspection, retained diagnostic evidence, and fresh controlled SDK probes. This did not run the interactive t3 triage command.
Activity
Alb11747 commentedon Oct 19, 2026
The safe tool-boundary implementation is now in #21158. It uses root PostToolBatch/Stop hook context, with a five-second durable queue fallback and cancellation/ownership checks. Focused SDK probes and regression tests are recorded there; final independent GPT-6.1 Sol xhigh review has no remaining findings. Fork checks are running. No build has been installed and T3 has not been restarted.
juliusmarminge commentedon Oct 21, 2026
Triaged against main at 70e5e384da. Scanned open issue titles for agent steering, tool cancellation, and user-refusal wording; no matching duplicate found. Runtime reproduction was not performed. The proposed fix changes agent-originated t3_thread_send steer/queue delivery semantics, which this automated triage flow reserves for maintainer review. Leaving open for Julius; no implementation changes.
juliusmarminge commentedon Oct 22, 2026
Reproduced on Linux against main 8acbefefc5, using the isolated web dev server and Claude Opus 5.5. An agent-originated explicit steer interrupted all three parallel 45-second t3_thread_wait calls immediately; a separate running Bash command was also aborted. The MCP calls returned interruption errors rather than the older user-refusal wording. Runtime transcript. Existing #21158 remains open and unchanged.
immakermatty commentedon Oct 24, 2026
One more failure mode with the same cause, in case it helps the maintainer takeover of #21158: an agent message can also cancel a pending question or approval. The person never sees the question.
What we observed (a fork based on v0.1.1, 2026-10-24, Claude Opus 5.5 in Claude Code):
AskUserQuestion.t3_thread_sendwith the defaultmode: auto.Tool permission request aborted, and the question disappeared from the UI before the person answered it.The same path on
main(909e4c9):ThreadManagementService.sendToThreadturnsautointosteer_activewhenever a steerable run exists.awaitClaudeUserInputAnswersresumes with empty answers, andawaitClaudeApprovalDecisionresumes withcancel.As far as I can read it, the takeover's guard (agent sends to a session with
activeSteeringInterruptsToolsgo to the durable queue) covers this case for Claude, so no separate fix is needed.Our interim fix: until it lands, our fork queues an
autosend while the thread has a pending runtime request, with a focused test (commit). We will drop it once #21158 is in a release. I have not checked whether steering a Codex turn affects a pendingrequest_user_input, so I am not proposing that broader guard here.Prepared through T3 Code with Claude Opus 5.5.
guryanov-a commentedon Nov 2, 2026
Human steers from the composer hit this too. As taken over, #21158 leaves human steering unchanged, so this case remains once it lands.
Seen on T3 Code 0.1.6, Claude Opus 5.5, Claude Code 2.1.316 (from the thread's activity log, not a controlled reproduction). A
Bashcall and aToolSearchwere issued together. I sent a steer 19 s into theBashcall.Bashfinished normally, and at that same moment the waitingToolSearchreturned "The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed." The agent followed my steer, but abandoned the work that call belonged to and told me "you stopped me".Claude Code 2.1.316 also ships neutral texts for this situation, such as "[Tool call did not complete: the turn was ended to deliver the message that follows. Nothing refused it; re-run it if still needed.]", but this steer got the refusal text. Delivering human steers at the next tool boundary (#15287) would avoid the cancellation altogether.
jamesdchen commentedon Nov 3, 2026
Same bug on T3 Code 0.1.6 (Windows x64, Claude Opus 5.5), from an orchestration run with about 15 Claude delegates. A coordinator thread steers delegates with
t3_thread_send(mode: auto), and the delegates read the cancellation as a human decision. No human saw or refused anything.Counts from one evening's provider logs: 14
tool_results withis_error: trueand this text, across 5 threads, within about 30 minutes: 13Bash, 1t3_thread_send. Each came 1-25 s after the call was issued.Why it costs more than one retried call:
Bashhad written local files and was piping them to a remote host, so the agent had to work out what had actually happened before it could continue.What would help beyond the queueing in #21158:
t3_thread_senddescription:steerandautocan kill the receiver's running tool. Agents currently have no way to know, andqueuelooks like merely a slower option.Caveat: I matched these to steers by timing and by the coordinator's send log. I don't have a per-call record tying each one to a specific
priority: "now"frame. I can share redacted log lines if useful.Meorir commentedon Nov 3, 2026
Frequency data and controlled runs for this bug, from a setup where many Claude threads coordinate through
t3_thread_send. The main additions:queuecaused no interruptions in our data, the outcome depends on the tool's state, and one outcome is an empty error that earlier comments don't mention.Environment: T3 Code desktop on Windows 11, now 0.1.6 with Claude Code 2.1.317. The 14-day window also includes earlier T3 builds and Claude Code back to 2.1.304.
steer_activeforautoandpriority: "now"are still onmaintoday.How often (14 days to 2026-11-03)
queue-operationin the transcript; this count comes from a first pass and includes a few Esc interrupts).Interruptions by the mode of the incoming message, matched on exact message text:
auto(explicit or default)steerqueue"No matching send" is probably human composer steers, wake-ups and senders outside these transcripts. We did not separate them. The send counts include idle and non-Claude recipients, so they don't give a per-busy-thread rate.
What the agent receives
The user doesn't want to take this action right now. STOP what you are doing...Error calling tool (Bash):with nothing after the colonThe tool call was interrupted before a result was received. It may or may not have completed...Command running in background with ID: ...The neutral wording mentioned above ("Nothing refused it") appeared 0 times. All 146 empty errors and all 209 STOP results have no
permissionDecisionin the transcript, while 106 of the 108 remaining interrupted MCP calls have one. The 146 and the 209 span Bash, Edit, Write, PowerShell and MCP. That is why we read the empty error as "interrupted before the permission decision", but this is an inference.The empty error is the hardest case for an agent: it has no reason text, so the agent can't tell what happened or whether the command ran.
Controlled runs
Each run used a Claude Haiku 5.5 child thread with a 1 s tick script and a
Writequeued behind it. The parent sent the message, on 0.1.6 with Claude Code 2.1.317.WriteWritesteer, about 12 s inWritesteer, about 12 s inWritequeueSo in the PowerShell case the agent reads a refusal while its first command is still running. A retry of that command would run it twice. Afterwards, the child in the first run described its write as having been rejected.
Three more steers during 16 sequential short PowerShell calls: one moved a just-started command to the background, and two landed between calls and cancelled nothing.
Cost
About one interruption in eight (35) was followed by more than 60 s before the agent's next tool call, and 16 by more than 5 minutes. That is an upper bound, because some of those gaps are ordinary ends of turns. In one time-boxed rollout, an agent waiting on what it read as a human refusal contributed to a 48-minute delay.
What would help
queuenumbers support its direction, but it currently has merge conflicts and was last updated on 25 Oct.t3_thread_senddescription thatautoandsteercan cancel running tools. Agents can't learn this today, and it's what we have just added to our own instructions as a workaround (mode: "queue"by default). We have no results from that yet.These are aggregates from local transcripts. The transcripts contain business data, so we can't attach them, but we can run other counts on request.
Prepared through T3 Code with Claude Opus 5.5.
bootherr commentedon Nov 4, 2026
As #15351 noted, Claude Code's neutral text for a call that a
"now"steer skips is behind a feature flag that's off by default. 2.1.317 added an env switch that turns it on. The result is picked here (2.1.317, minified):hCis the "The user doesn't want to take this action right now. STOP…" text.vjeis "[Tool call skipped: the turn ended to deliver the message that follows before this call ran. Nothing refused it; re-run it if still needed.]".I ran the real 2.1.317 CLI offline (stream-json,
--permission-prompt-tool stdio, mock API, two parallel calls to a slow MCP tool) and sent a"now"message while the first call was running:CLAUDE_CODE_FIZZY_GROVE=0or empty: the waiting call gets the STOP textCLAUDE_CODE_FIZZY_GROVE=1ortrue: it gets the "Nothing refused it" textinterruptcontrol request) still reads as a stop with the switch on, because it aborts withremote-cancel, notinterruptDefaulting
CLAUDE_CODE_FIZZY_GROVE=1inmergeClaudeProcessEnvironmentwould fix the wording for agent and composer steers alike. Otherinterruptaborts can also selectvjefor skipped calls, and the switch is undocumented. I haven't tested how a real model reacts to the new text, or Meorir's empty-error case.I have the change drafted with tests and can open a PR if you want it, and I'm happy to share the repro scripts. Until then, setting the variable in a Claude instance's environment works on 2.1.317.
Prepared through T3 Code with Claude Opus 5.5
OscarSwanros commentedon Nov 4, 2026
Another case for the tool-boundary delivery: a bridge that relays a person's chat
messages (WhatsApp, a phone app) into a T3 thread with
t3 thread send. Thosemessages are often that person correcting the task the thread is running.
Today
t3 thread sendhard-codesmode: "queue"(apps/server/src/cli/thread.ts),so a correction waits for the whole turn. Passing
autoinstead would hit thisissue and cancel the running tools. And with #21158 as it stands, sends through
MCP are
createdBy: "agent", so they would queue anyway.What would serve this case: the PostToolBatch/Stop delivery described above, for
sends like these, and a
--modeoption ont3 thread sendthat passes throughto
t3_thread_send's existing modes. Would a focused PR for that option bewelcome once delivery is safe?
bompus commentedon Nov 5, 2026
Measured on 2026-11-05 on a Linux host (local T3 build; version not re-queried), Claude thread running Sonnet 5.5, one foreground Bash call
date +%T.%N; sleep 75; date +%T.%N.t3_thread_sendinqueuemode 8 s into the call: the call ran the full 75.0 s with no interruption, and the message started a new run after the turn ended.steermode: delivered at 17:46:40.6Z; the call failed 5.0 s later (17:46:45.7Z) withError calling tool (Bash): interrupt.So steering a Claude thread interrupts a running foreground tool call after a fixed ~5 s, which matches the
priority: "now"diagnosis above, whilequeueis safe. In one long-lived thread transcript, 16 of 18 interrupted Bash calls were exactly 5.0 s after a queued message entry. Settings > General > Follow-up behavior (Queue or Steer) decides which path a message typed into a running thread takes.mitschabaude commentedon Nov 9, 2026
@juliusmarminge to me this issue effectively means that steering claude in t3 code doesn't work. in my experience, steering always interrupts tool calls and gives claude the impression that the I was unhappy about what it did, which confuses it and causes it to end up in unproductive circles
mcarkade commentedon Nov 9, 2026
Also seeing this on T3 Code 0.1.8-nightly.20261108.4353 (Windows 11) with Claude Code 2.1.319. Follow-up behavior is set to Steer.
t3_thread_sendin the defaultautomode.On
mainat 4f0b74e, the Claude steer still goes out withpriority: "now"(ClaudeAdapterV2.ts:11672).CLAUDE_CODE_FIZZY_GROVEisn't set anywhere, so the cancelled call still gets the refusal text. #21158 covers only agent-sent messages, so steers typed in the composer would still hit this after it lands.Switching to Queue avoids it, but then Steer can't be used on Claude threads.
Posted on my behalf by Claude Opus 5.5 in T3 Code.
1kuna commentedon Nov 9, 2026
Title: Additional cancellation counts: even a peer acknowledgement can look like human refusal
Problem: An agent-originated
t3_thread_sendin defaultautomode can steer a running Claude turn and cancel pending tool calls. The receiver then interprets the cancellation as a human refusal and stops, despite the trigger being an ordinary peer acknowledgement.Reproduction or observation: A retained local-transcript audit counted 19 instances in one orchestrator transcript since October 24, and 28 across the inspected transcripts. These populations overlap; they should not be added. In one case, three outbound sends were cancelled 3 milliseconds after a peer's one-line acknowledgement was enqueued. This is timing evidence from retained records, not a controlled reproduction of every instance or a fresh test on current main.
The cancelled results had
toolDenialKind: "cancelled"and Claude Code's text: "The user doesn't want to take this action right now. STOP what you are doing and wait for the user…" A genuine denial in the inspected records used different wording: "doesn't want to proceed with this tool use". Receivers stopped because they read the cancellation as the human declining the action.Proposed behavior: Queue agent-originated messages by default unless the sender explicitly requests steering, or deliver them at a safe boundary as proposed in #21158. Where steering still cancels a call, provide trusted turn context explaining that an arriving message caused the cancellation, so receivers can distinguish it from an actual human denial. Preserve deliberate human refusal and Stop semantics.
The quoted refusal wording is generated by Claude Code, not T3; changing that upstream wording itself is outside T3's scope. T3 can control delivery timing and expose the cause/provenance of its own steering. #21158 remains open and unmerged at this check.
Why native: T3 knows the authenticated message origin and chosen delivery mode. A harmless acknowledgement should not acquire the meaning of a human veto solely because it arrived during a tool batch.
1kuna commentedon Nov 9, 2026
Correction to my earlier report: the interruption lever is directly under T3’s control. In the installed T3 bundle,
ClaudeAdapterV2.steerTurnhard-codespriority: "now"in its outgoing Claude user message. That priority causes the turn interruption; arriving stream-json messages need not all behave this way.Fresh headless measurements on Claude Code 2.1.321, routed Haiku 5.5, isolated config/disposable working directories, three serial eight-second MCP tool calls emitted in one assistant reply. A second stream-json user message was sent about 0.8 seconds after the tool batch was emitted:
priority: "now", flag unset: one in-flight call was interrupted; the two pending calls each returned exactly:The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed.priority: "now",CLAUDE_CODE_FIZZY_GROVE=1: the same two pending calls each returned exactly:[Tool call skipped: the turn ended to deliver the message that follows before this call ran. Nothing refused it; re-run it if still needed.]The flag fixes the wording; it does not prevent interruption.priority: "next", flag unset: all three calls completed withSLOW_OK; none was cancelled, and the second message was acknowledged.In both
nowcases, the in-flight call returned exactly:The tool call was interrupted before a result was received. It may or may not have completed on the server — verify before assuming it succeeded, and retry if needed.This is distinct from the pending-call wording above.Proposal: use native
nextfor agent-originated messages, preservingnowfor human input. The measured pending-call cancellation wording can also be made accurate today withCLAUDE_CODE_FIZZY_GROVE=1, but this is an undocumented, experimental Claude Code flag and should be re-checked after upgrades. These probes exercise the CLI protocol; they are not an end-to-end test of T3 queue ownership/cancellation or the proposed delivery change.