Skip to content

Agent steers on Claude threads cancel tools and look like user refusals #21895

Open

Description

@Alb11747

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

  1. Start a Claude thread and have it run a command or parallel tool calls.
  2. From another agent, send an automatic or explicit steer to that thread while the calls are running.
  3. Observe the tool cancellation and misleading user-refusal result. With the initial fix(server): agent messages no longer cancel Claude 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 reported queued_turn_count: 0 despite the continuation. Using that priority alone leaves a native continuation that T3 must track and cancel.
  • PostToolBatch.additionalContext delivered 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.additionalContext let 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

  1. Alb11747 commented on Oct 19, 2026

    @Alb11747
    ContributorAuthor

    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.

  2. juliusmarminge commented on Oct 21, 2026

    @juliusmarminge
    Member

    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.

  3. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    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.

  4. immakermatty commented on Oct 24, 2026

    @immakermatty
    Contributor

    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):

    • A coordinating Claude thread asked the person through AskUserQuestion.
    • While the question was pending, another thread's message arrived via t3_thread_send with the default mode: auto.
    • The tool call returned Tool permission request aborted, and the question disappeared from the UI before the person answered it.
    • It happened five times in one thread in one day. A question asked while no message arrived went through normally.

    The same path on main (909e4c9):

    • ThreadManagementService.sendToThread turns auto into steer_active whenever a steerable run exists.
    • The steer aborts the SDK callback, and the pending request resolves without an answer. awaitClaudeUserInputAnswers resumes with empty answers, and awaitClaudeApprovalDecision resumes with cancel.

    As far as I can read it, the takeover's guard (agent sends to a session with activeSteeringInterruptsTools go 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 auto send 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 pending request_user_input, so I am not proposing that broader guard here.

    Prepared through T3 Code with Claude Opus 5.5.

  5. guryanov-a commented on Nov 2, 2026

    @guryanov-a

    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 Bash call and a ToolSearch were issued together. I sent a steer 19 s into the Bash call. Bash finished normally, and at that same moment the waiting ToolSearch returned "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.

  6. jamesdchen commented on Nov 3, 2026

    @jamesdchen

    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 with is_error: true and this text, across 5 threads, within about 30 minutes: 13 Bash, 1 t3_thread_send. Each came 1-25 s after the call was issued.

    "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."
    

    Why it costs more than one retried call:

    • Agents obey it. The text says a human refused, so a well-behaved agent stops and doesn't retry. One delegate stopped twice and asked its coordinator for permission to proceed, and work held up behind it waited (116 lines of a queued batch, the second time).
    • It reads as a policy decision. The agent took it as "the user objects to this kind of action", and reasoned about what the user might have meant. A coordinator can't tell it apart from a real refusal. I asked the human directly whether they'd clicked anything, and they hadn't. Nothing in the thread's UI or the message says it was another agent's steer.
    • Compound commands get cut mid-way. One cancelled Bash had 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:

    • When the cancel comes from a steer, say so in the tool result: for example, "Cancelled because a message arrived. Nobody refused this. Read the message, then re-run if it's still needed." That removes the false attribution even where cancellation can't be avoided.
    • Document it in the t3_thread_send description: steer and auto can kill the receiver's running tool. Agents currently have no way to know, and queue looks 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.

  7. Meorir commented on Nov 3, 2026

    @Meorir

    Frequency data and controlled runs for this bug, from a setup where many Claude threads coordinate through t3_thread_send. The main additions: queue caused 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_active for auto and priority: "now" are still on main today.

    How often (14 days to 2026-11-03)

    • About 460 tool calls cancelled in about 285 interruptions, in 62 of 1,109 local transcripts (subagent transcripts count separately).
    • 2 to 23 interruptions a day until 1 Nov, then 101 on 2 Nov, a day of heavy cross-thread coordination, and 45 so far on 3 Nov.
    • 433 of 466 cancelled results came within 1 s of a message entering the receiver's queue (queue-operation in 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:

    Mode Sends Interruptions
    auto (explicit or default) 5,188 234
    steer 40 4
    queue 2,680 0
    No matching send n/a 47

    "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

    Call state when the steer arrives Result text Count Effect
    Waiting behind another tool call The user doesn't want to take this action right now. STOP what you are doing... 209 Did not run in our controlled runs
    No permission decision recorded yet Error calling tool (Bash): with nothing after the colon 146 Probably never started; not reproduced in a controlled run
    MCP call already past permission The tool call was interrupted before a result was received. It may or may not have completed... 106 Unknown, by its own wording
    Foreground PowerShell running Command running in background with ID: ... 21 Keeps running
    Foreground Bash running Normal result, once the command ends n/a The steer waits for the command

    The neutral wording mentioned above ("Nothing refused it") appeared 0 times. All 146 empty errors and all 209 STOP results have no permissionDecision in 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 Write queued behind it. The parent sent the message, on 0.1.6 with Claude Code 2.1.317.

    Run Sent Script Write Message delivered
    PowerShell + Write steer, about 12 s in Moved to background, kept ticking to its normal end 84 s later STOP, file not created At once
    Bash + Write steer, about 12 s in Finished normally STOP, file not created After the script ended, 53 s later
    PowerShell + Write queue Finished normally Succeeded 5 s after the tools finished, as a new turn

    So 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

    1. A status on fix(server): agent messages no longer cancel Claude tools #21158. Our queue numbers support its direction, but it currently has merge conflicts and was last updated on 25 Oct.
    2. A warning in the t3_thread_send description that auto and steer can 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.
    3. +1 to the earlier suggestion of saying in the tool result that a message caused the cancellation, including for the empty error.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions