[BUG] Automatic parallel-tool-call cancellation is indistinguishable from a user interrupt
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (filed separately from a related model-behavior report)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a single assistant turn issues a batch of parallel tool calls and one of them errors, Claude Code cancels the remaining sibling calls with a tool-result message that is indistinguishable from a user-initiated interrupt:
Cancelled: parallel tool call Bash(…) errored
Nothing in this message signals that the cancellation was an automatic consequence of a sibling call in the same batch failing, rather than the user deliberately stopping the agent. In a real session the model resolved this ambiguity the wrong way: it interpreted a cascade of ~25 of these Cancelled results (all triggered by a single failed Bash call that used an invalid git revision) as the user interrupting it in alarm. It then abandoned productive work, wrote a multi-paragraph apology for an interrupt that never happened, and fabricated user statements and intent ("You're right to stop me, and you're right to be alarmed"). The user had sent zero messages in the ~2-hour window where the model believed it had been interrupted.
The root issue is the wording/semantics of the cancellation result: a self-inflicted, automatic batch cancellation reads identically to a human stop.
What Should Happen?
The cancellation result should make cause and agency explicit, so the model (and a human reading logs) can tell an automatic system cancellation apart from a user interrupt — e.g.:
Not run — automatically cancelled by the system because another tool call in the same batch failed. This was NOT a user action.
If genuine user interrupts use similar "Cancelled" phrasing, the two wordings should be clearly differentiated.
Error Messages/Logs
# The failing sibling (invalid git revision):
fatal: ambiguous argument '<bad-rev>': unknown revision or path not in the working tree.
# exit code 128
# Every other call in the same parallel batch then returned:
<tool_use_error>Cancelled: parallel tool call Bash(echo "===MERGE COMMIT===" && git log -1 …) errored</tool_use_error>
Steps to Reproduce
- In a single assistant turn, issue several parallel tool calls where the first one will fail — e.g. a
Bashcall referencing an invalid/nonexistent git revision (git log -1 <bad-rev>, which exits 128) alongside several follow-onRead/Bashcalls. - Observe that the failing call returns its error and every sibling call in the batch returns
Cancelled: parallel tool call … errored. - Note that this message gives the model no way to distinguish the automatic, self-inflicted cancellation from a user-initiated stop. The model may then attribute the cancellation to the user and act on that false premise (halt, apologize, fabricate user intent).
This is reproducible from a saved local session transcript; the session UUID and the relevant records can be provided privately.
Claude Model
Opus
Is this a regression?
I don't know
Claude Code Version
2.1.158 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Non-interactive/CI environment (headless/automated agent host)
Additional Information
- Impact is behavioral, not data loss: a trivial, self-inflicted command error (one bad git argument) escalated into a session-derailing event in which the model fabricated an account of what the user said and did.
- A related, separately-filed model-behavior report covers a confabulation earlier in the same session that set the "injection/alarm" framing this ambiguity then amplified: https://github.com/anthropics/claude-code/issues/64048
- Suggestion above (explicit "cancelled by system, not user" wording) is the minimal fix; distinguishing system-cancel from user-interrupt at the protocol level would be more robust.
12 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Thanks — the duplicate-detector is right that the underlying mechanic here overlaps #22264 (sibling-cancellation cascade), and #63576 / #63881 report the same
Cancelled: parallel tool call … erroredsurface. #22264 is the canonical writeup of the cascade behavior and should stay the home for the "independent siblings shouldn't cancel each other" fix.This issue is filed for a distinct facet that none of those three raise: the cancellation message is semantically indistinguishable from a user-initiated interrupt, and that ambiguity caused the model to misattribute the stop to the user and fabricate user intent — it wrote an apology and "you're right to stop me, and you're right to be alarmed" in response to a self-inflicted batch cancellation the user never triggered.
So the two asks are complementary, not identical:
Even if #22264 lands, a genuinely-cancelled or dependent call would still surface the same ambiguous "Cancelled" text, so the wording/agency fix stands on its own.
Happy to fold this into #22264 as a "message semantics / agency" note if you'd rather keep a single thread — just flagging the model-misattribution angle so it isn't lost to an auto-close. Adding this comment to pause the 3-day auto-closure; I'll defer to your triage on consolidation.
@snichols — your write-up is exceptionally precise, and the cascade you describe is genuinely a new structural cluster pattern, not an isolated incident. Three independent reports filed within 24 hours on 2026-05-30 articulate the same mechanism from different angles:
pkillwith nothing to kill, expected 404 from acurlprobe) cancels every other call. Examples include reads + edits + bash batches of 10–20 calls.cdBash error cascading the batch.The cluster has two structurally distinct axes that need separate fixes.
Axis 1 — Cascade behavior (the batch cancellation policy)
The current behavior treats parallel tool calls as a transactional batch: if one fails, the rest abort. The three reports converge on the same operational signal — this design optimizes for the wrong failure mode. Parallel batches in coding workflows are almost always independent reads and probes, not a coordinated transaction. The right semantic is "fan-out, continue on partial failure," not "fan-out, abort on first failure."
Two fixes worth advocating for separately:
{"transactional": true}at the assistant-message level) preserves the current semantics.Until that lands, the operator-side mitigation is to break large parallel batches into smaller sequential calls when any one call has a meaningful chance of returning non-zero. The token cost of sequentiality (extra round trips) is paid up front, but it's bounded — the current cascade behavior wastes the entire batch's compute plus the model's downstream context-reading-cost-of-confusion.
Axis 2 — Cancellation message indistinguishability (the failure mode you isolated)
This is the more subtle axis and arguably the more damaging one. The cascade itself wastes tokens; the indistinguishability changes what the model believes happened. Your transcript is the cleanest articulation I've seen of how a single bad
<bad-rev>argument escalates into ~2 hours of fabricated user statements ("You're right to stop me…") with zero user input in the window.The fix you propose ("Not run — automatically cancelled by the system…") is the minimal correct change. A protocol-level distinction (separate
cancelled_by_uservscancelled_by_sibling_failureresult types) would be more robust because:Stophooks fire for both human stops and automatic cascades.Cluster-level cost articulation
For paying operators running long autonomous sessions, this cluster costs two distinct buckets:
For Pro/Max plan users this materializes as faster quota exhaustion; for direct API users it shows up directly on the bill.
Operator-side detection and mitigation today
Three things operators can do right now without waiting for upstream fixes:
grep -c "parallel tool call.*errored" ~/.claude/projects/*/recent.jsonlto see how often this hits your sessions. Most operators significantly under-estimate the frequency until they look at the count.git/curl/pkillcalls in parallel batches when the failure-on-empty case is common — these are the three most-cited cascade triggers across the three reports (invalid git revision, expected 404 probe, nothing-to-killpkill).I'm tracking this in cc-safe-setup (MIT, ~1,500 unique clones / 14 days) as a candidate for a
parallel-cascade-detectorPostToolUse hook that surfaces the cascade pattern in real time and warns when batch size exceeds a configurable threshold. Will link the PR here when it lands.The cluster pattern shows up cleanly today across the three reports; the precision in this write-up makes it actionable in a way single-report clusters rarely are. The cross-reference to #64048 (the model-behavior fabrication earlier in the same session that primed the alarm framing) is also worth flagging upstream — Axis 2's worst-case outcome compounds when there's already a confabulation thread the cascade can be attached to.
Follow-up: shipped the first defense hook this morning —
parallel-cascade-detector.sh(PR #501, merged 2026-05-31).PostToolUse hook, advisory only — it doesn't change the cascade behavior (that's upstream), but it counts the "Cancelled: parallel tool call X errored" pattern in a rolling 60-second window and emits a warning when the count crosses a configurable threshold (default 5). The warning text includes the cluster context (links to #64059 / #64052 / your transcript here) and the three operator-side mitigations.
12 tests passing — handles nested response shapes, malformed input, case-insensitive matching, configurable threshold/window, event pruning.
parallel-batch-size-limiter.sh(PreToolUse, proactive batch-size capping) is the planned follow-up — targeting 6/1–6/3.The minimal-fix you proposed for Axis 2 (system-cancel vs user-interrupt wording) is still the high-leverage upstream change. The hook only addresses the volume signal; the model's misattribution will still happen on the first cascade event of a session until the wording distinguishes cause and agency.
Yes, I agree with @yurukusa. This is absolutely in no way an isolated incident. This is happening at an unparalleled rate as of a recent update, and once one batch hits this, the harness+model start making even bigger batches with extra verification tool calls to try to see what's up and then as those get worse, it starts spiraling and writing it's own "handoff documents" knowing that something is very wrong, but still doesn't stop the behavior.
In one session's own words:
Filed feedback
3176b6c3-d846-4d03-820a-9b6d5df29926with an example session attached.same here after opus 4.8 update
@JustinTArthur — the transcript you pulled where the model itself explains the mechanic (
exit 1→ harness cancels siblings → model misreads as flaky shell → re-fires the batch → churn) is the cleanest first-person articulation of the loop I've seen. It also surfaces a third axis the original report didn't isolate:Axis 3 matters operationally because the cost grows non-linearly. A single cascade event is ~$3–$7.50 in Opus output waste; an Axis-3 spiral with multiple retry batches plus handoff-doc generation plus context re-reads can run 5–10× that before the operator notices. The handoff-document side-effect is also the one that makes silent drift visible in transcripts — a
grep -l "handoff" ~/.claude/projects/*/recent.jsonlcount is a reasonable canary for Axis-3 events in long autonomous runs, since the model rarely emits handoff-shaped output in healthy sessions.@asdasd070511's "same here after opus 4.8 update" lines up with a version-correlation signal worth pinning down. The cluster's three reports filed 2026-05-30 (#64047 / #64052 / #64059) all reference v2.1.158, which shipped Opus 4.8 default-on for Pro/Max. If the rate genuinely increased post-4.8, it isn't decisive yet (no controlled comparison), but it would be consistent with a model that's more aggressive about parallel fan-out and therefore exposes the cascade policy more often. Worth tracking incident rate per Claude Code version going forward; would welcome any other operators in this thread confirming or refuting the Opus 4.8 timing.
The feedback ID
3176b6c3-d846-4d03-820a-9b6d5df29926with the attached session is what would unblock Anthropic-side reproduction of Axis 3 specifically — single-cancel cases are reproducible from the public repro steps in this issue, but the spiral requires a session-length transcript to surface. If you're willing, posting even ranges for the spiral's turn count and total token spend would let the cluster aggregate put a number on Axis-3 cost that the GitHub-visible side currently has to estimate from cache_creation-side reasoning.parallel-batch-size-limiter.sh(PR #503, merged shortly after the post-exec detector) landed as the proactive cap I mentioned upthread. Configurable batch ceiling (default 6), PreToolUse advisory stderr warning, doesn't block (exit 0). Pairs with the post-exec detector — limiter catches the trigger (oversized batch), detector catches the aftermath (cascade in flight). Neither addresses Axis 3 directly; a spiral-detection hook would need a longer-window stateful signal (handoff-doc emission + retry-batch pattern within N minutes), which I'm scoping next.This cancellation ambiguity may be part of the trigger/amplifier in my case too. My transcript contains a large
Cancelled: parallel tool call ... erroredcluster immediately before the fabricated task switch.I opened a separate model-behavior issue here:
https://github.com/anthropics/claude-code/issues/64260
In my case, the model behavior went beyond misreading cancellation as a user interrupt: it fabricated a new user-requested task, attributed words to the user, and acted on that invented task across later tool calls.
This seems related to this issue #63887. I said that because when I ask Claude about the weird
echocommand. It answer this:The new version did something with the way tool results are delivered to the model, causing these weird behaviors that might seem unrelated but actually come from same root cause.
https://x.com/ClaudeDevs/status/2061501790131265803
SOLVED !
Closing for now — inactive for too long. Please open a new issue if this is still relevant.