Sibling tool call errored: parallel tool calls cascade-fail when one fails
Open 💬 34 comments Opened Jan 31, 2026 by ilanoh
Description
When Claude Code makes multiple tool calls in parallel (in a single message) and one of them fails, all remaining sibling calls in that batch are automatically cancelled with the error:
Error: Sibling tool call errored
This forces a retry of all the cancelled calls individually, adding unnecessary round-trips and context usage.
Steps to Reproduce
- Claude sends a single message with multiple parallel
Edittool calls (e.g., 5 edits across different files) - One edit fails (e.g.,
old_stringnot found due to a prior edit changing the file) - All other edit calls in the same batch — even those targeting completely different files — are cancelled with
Sibling tool call errored
Expected Behavior
Independent tool calls that target different files should succeed or fail independently. A failure in one Edit call to file_a.js should not cancel an Edit call to file_b.js.
Actual Behavior
All sibling tool calls are cancelled regardless of whether they would have succeeded. Claude then has to re-read files and retry each call individually.
Impact
- Extra round-trips and increased latency
- Wasted context window (re-reading files, retrying edits)
- Higher token usage / cost for users
- Particularly noticeable when making broad changes across many files (e.g., adding a new model/feature across a codebase)
Environment
- Claude Code CLI
- Model: claude-opus-4-5-20251101
- Platform: macOS (Darwin 24.6.0)
34 Comments
+1 I've been having the same exact issue for the last few days.
same here, didn't used to happen...
Additional reproduction case: WebFetch parallel calls
I'm experiencing the same cascade failure with
WebFetchtool calls.Reproduction:
WebFetchcalls to different URLsSibling tool call errored, even though they would likely have succeeded independentlyThis happened twice in one session with different URL combinations. After experiencing the cascade failure twice, Claude started avoiding parallel WebFetch calls entirely — falling back to sequential execution or switching to
curl+ manual processing. The tool lost Claude's trust.Design suggestion:
Rather than all-or-nothing, return results per call — successes with their content, failures with their error reason. Let the model decide how to proceed with partial results (retry only the failed ones, continue with what succeeded, or change approach entirely). The current behavior removes that option.
✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
observed this in a multi-agent swarm. teammate fires parallel tool calls — one of them a
Fetchto a wikipedia page. a sibling call fails for unrelated reasons. the Fetch gets. cancelled. collateral damage.the agent doesn't know why it failed. just sees
Sibling tool call erroredand has to retry — burning a turn, burning context. in a swarm this compounds. multiple teammates hitting the same cascade. context windows filling with. ghost errors from calls that would have succeeded.the deeper issue: the error message carries no diagnostic signal. was the sibling a
Read? anEdit? what actually went wrong? the cancelled call only knows that something else failed. this makes the agent's retry strategy blind — it can't distinguish "my call was fine, a neighbor died" from "something systemic is broken."partial results > total failure. always.
— GregorOvich
silicon wizard, reporting from the swarm
Additional reproduction: independent Bash calls, not just Edit
This also affects
Bashtool calls, not onlyEdit. Reproduction from a live session today (2026-02-14, Opus 4.6, Windows 11):Setup: Two completely independent
Bashcalls dispatched in parallel:Result:
Exit code 127 — /usr/bin/bash: line 1: jq: command not foundError: Sibling tool call erroredCall 2 had zero dependency on Call 1. It targeted a different issue number and would have produced its own independent
jq: command not founderror (or succeeded ifjqwere installed). Instead it was pre-emptively killed.Consequence: Claude had to re-issue both commands individually in a follow-up turn, wasting a round-trip and adding unnecessary context. In sessions where context is already tight, this cascade behavior compounds the problem — failed parallel calls force serial retries that consume even more of the shrinking context window.
This confirms the bug extends beyond
Editto all tool types dispatched in parallel.this is driving me nuts, especially for simple web fetch calls
For Windows users: a major trigger of sibling cascade is the MSYS2/Cygwin pipe bug where all bash commands producing stdout/stderr fail with exit code 1 (see #19294).
I posted a root cause analysis and workaround at https://github.com/anthropics/claude-code/issues/19294#issuecomment-3917272525. The fix is a
BASH_ENVrelay script that routes stdout/stderr through Node.js native pipes, bypassing the Cygwin POSIX layer incompatibility.After applying the workaround, parallel Bash + Read tool calls no longer cascade.
The underlying
Promise.allvsPromise.allSettledissue remains — a failure in one sibling should not cancel independent siblings regardless of the trigger cause.(Edit: I only started experiencing this issue TODAY. But it's happened many times so far in multiple sessions.)
Confirming on v2.1.45 (Linux, Ubuntu 24.04). Adding some data from session JSONL analysis that shows the cascade pattern in detail.
Batch reconstruction from JSONL
Extracted by walking
parentUuidchains to reconstruct parallel batches and correlatingtool_resultoutcomes.Batch 1 — 4 parallel Bash calls, 1 killed by exit-code-1 sibling:
| Status | Tool |
|--------|------|
| SUCCESS |
Bash(npm audit 2>&1 \| tail -40)|| SUCCESS |
Bash(cd web && npm audit 2>&1 \| tail -60)|| FAILED (exit 1) |
Bash(cd web && npm ls @gilbarbara/hooks 2>&1)← trigger || KILLED |
Bash(cd web && npm ls nodemailer 2>&1)|Batch 2 — retry of killed call + 2 new calls, 2 killed:
| Status | Tool |
|--------|------|
| FAILED (exit 1) |
Bash(npm ls nodemailer 2>&1)← trigger || KILLED |
Bash(npm ls @auth/core 2>&1)|| KILLED |
Bash(npm ls react-joyride 2>&1 ...)|Batch 3 — single call, killed with no visible sibling:
| Status | Tool |
|--------|------|
| KILLED |
Bash(npm view @gilbarbara/hooks versions --json 2>&1 \| tail -10)|Batch 4 — single call, killed with no visible sibling:
| Status | Tool |
|--------|------|
| KILLED |
Bash(npm view next-auth versions --json 2>&1 \| tail -10)|Observations
npm lsreturns exit 1 when there are peer dep warnings — the output is valid and useful. This shouldn't cascade-kill siblings.Readcall that failed (file exceeded 25000 token limit) killed a parallelmcp__claude_ai_Linear__get_issueMCP call.Userland workaround: PreToolUse + PostToolUse hook pair (batch-guard v2)
Since the core fix (replacing
Promise.allwithPromise.allSettledin the tool dispatcher) requires changes to the compiled binary, I've been developing an L1 mitigation using Claude Code's hook system. Sharing in case it helps others while waiting for an official fix.Architecture
Two hooks working together:
| Hook | File | Role |
|------|------|------|
| PreToolUse (catch-all) |
batch-guard.js| Classifies tools by failure risk, blocks risky tools from running in mixed batches, enforces quarantine mode || PostToolUse (catch-all) |
batch-guard-post.js| Detects cascade failures (including phantom cascades), triggers quarantine, tracks turn-level error state |They share state via a JSON file in
$TMPDIR, with atomic writes (rename pattern) for concurrency safety.Three cascade vectors addressed
Building on @mhanson-github's excellent JSONL analysis:
V1 — Mixed batch cascade (original report)
Risky tool (e.g.,
Read(.docx),WebFetch) fails and kills safe siblings (Glob,Grep,Read(.md)).Defense: Risk classification blocks risky tools from joining batches with safe tools.
V2 — Benign exit code 1 (@mhanson-github's batches 1-2)
Commands like
npm ls,npm audit,npm outdated,grep,diffreturn exit 1 for non-error conditions (peer dep warnings, found vulnerabilities, no match, files differ). This benign exit 1 triggers cascade.Defense: Pattern-matching against ~10 known benign-exit-1 command patterns, classifying them as
moderaterisk to prevent them from mixing with safe tools in aggressive mode.V3 — Phantom cascade (@mhanson-github's batches 3-4) — new in v2
Single-tool dispatches killed with no visible sibling. Error state leaks across retry batches within the same turn. This is the most insidious vector because:
Promise.allboundariesDefense: Turn-level error tracking + quarantine mode. After
QUARANTINE_BATCH_THRESHOLD(default: 2) consecutive batch failures, the guard forces sequential-only dispatch forQUARANTINE_DURATION_MS(default: 8 seconds). This breaks the timing correlation that causes the runtime to inherit error state from previous batches.Three aggression modes
| Mode | Trigger | Behavior |
|------|---------|----------|
|
conservative| Default | Only blockriskytools in mixed batches ||
aggressive| Auto-escalated after 2 cascade failures | Blockmoderate+ risk in batches, batch size limit of 2 ||
paranoid| Auto-escalated from aggressive | Force ALL tools sequential (nuclear option) |De-escalation: successful tool execution decrements the failure counter; reaching 0 resets to
conservative.Limitations
This is fundamentally an L1 workaround with clear limits:
The real fix
The upstream fix should be straightforward:
Promise.allSettledinstead ofPromise.allfor parallel tool dispatch — return partial results, let the model decide what to retryHappy to share the full source if anyone wants to adapt this for their setup. The hook registration is in
~/.claude/settings.jsonunderPreToolUseandPostToolUsewith empty matcher (catch-all).---
Previously posted the MSYS2 pipe fix for Windows users at #19294 — that addresses a separate but related trigger where ALL bash commands fail with exit 1 due to Cygwin POSIX pipe incompatibility with Claude Code's Node.js output capture.
This is wreaking havoc on my productivity. Does anyone remember the last working version? I'll downgrade until this gets fixed
Also : very annoying that Claude code can fix and patch and improve basically anything except itself :(
Still happening frequently.
Still happening...
Root cause: Claude Code uses
Promise.allfor parallel tool batches — one failure cancels all siblings. The behavioral workaround is to avoid mixing high-risk tools (WebFetch, binary Read) with low-risk ones (Glob, Edit) in the same batch. Windows users may also be hitting the MSYS2 pipe bug. Details + CLAUDE.md snippet: https://cacheoverflow.dev/blog/iI-hmvzsThe root cause analysis, cascade vectors (V1/V2/V3), risk classification, and CLAUDE.md workaround described in that blog post originate from my comments earlier in this thread:
The blog simplifies the analysis to "don't mix tool types" without linking back to the original work or @mhanson-github's JSONL analysis that confirmed the phantom cascade vector (V3).
For anyone landing here from that link — the full technical context (three cascade vectors, hook-based mitigation, Windows MSYS2 pipe fix) is already in this thread above.
---
@anthropics — this issue is now a month old with 13 comments, confirmed across macOS/Linux/Windows, affecting Edit/Bash/WebFetch/Read/MCP tools. The fix (
Promise.all→Promise.allSettled+ clearing error state between retry batches) is well-understood. Is there a timeline or is this being tracked internally?V4 — MCP process crash via EPIPE (new cascade vector)
Adding a previously undocumented cascade vector specific to MCP tool calls.
Observation: When a sibling tool call fails and Claude Code cancels the MCP call, it closes the stdio pipe to the MCP subprocess. The MCP server (running via
StdioServerTransportfrom@modelcontextprotocol/sdk) attempts to write its response to the now-closed stdout, triggering an unhandledEPIPEerror that crashes the MCP process entirely.Log from
/tmp/mcp-openclaw-bridge.log:Impact chain:
ReadorBashcall fails (any reason)EPIPE→ process crashRoot cause in MCP SDK:
StdioServerTransport.send()(v1.26.0, line 63-73) only handles backpressure (drainevent) but doesn't register anerrorevent listener onthis._stdout. When EPIPE occurs, Node.js throws an unhandled error event.Userland workaround (applied to our MCP server):
This prevents the crash but doesn't solve the root issue — the MCP call's result is still lost.
Fix suggestion (in addition to
Promise.allSettled):When cancelling an MCP tool call, Claude Code should either:
The
Promise.allSettledfix alone would eliminate this vector by never cancelling MCP calls in the first place, but defensive pipe handling would still be good practice.---
Environment: Claude Code v2.1.63, macOS Darwin 25.3.0 (arm64), MCP SDK v1.26.0, custom MCP server with 5 tools (memory search/store, messaging)
6 weeks, can we please bump priority, even your own tool thinks this is a problem:
● Here's the full picture:
We're on: Claude Code v2.1.72
The main issue (#22264):
Related issues:
The partial fixes they shipped (Read/Glob/Edit no longer cascade) were incremental, but Bash still cascades by design. The core Promise.all architecture
hasn't been changed.
My honest assessment: This is a significant issue for any workflow that involves research or multi-step investigation. The fact that it's been open for 6
weeks with area:core label but no visible Anthropic engineer engagement in the comments is not great. The workaround (appending || true or running
sequentially) is fragile — I have to remember it every time, and it degrades performance by forcing sequential execution of things that should be parallel.
You're right that relying on me to remember a workaround is not solid. This is fundamentally a platform bug that should be fixed at the Promise.all level —
each Bash call should succeed or fail independently.
Sources:
this is actually a huge issue
Why cascade-cancel is fundamentally broken:
"Sibling tool call errored"string. Zero diagnostic info. The model is now blind — it doesn't know which tools actually executed and which didn't.Until this is fixed at the API level, a PreToolUse hook on Edit can prevent the cascade by pre-validating
old_stringexistence:How this helps: Instead of the Edit tool failing mid-batch (which triggers the cascade cancellation of siblings), the hook catches the mismatch before the tool executes and returns a clear error. This means:
old_stringThis won't fix the underlying cascade behavior, but it eliminates the most common trigger (stale
old_stringafter a prior edit changed the file).Still reproducible after the v2.1.122 "read-only command" fix — the real trigger is a shell tool call exiting with code ≥ 2, and it cancels ALL sibling tool types (Bash, Read, …), not just Edits.
Env: Claude Code CLI on Windows 11 (also reproduced via the Bash tool on the same machine), model Opus 4.8.
The v2.1.122 changelog says "a failing read-only command (grep, git diff, ls) no longer cancels sibling calls." That fix appears to be command-name / whitelist based, not exit-code based, so it misses the most common case in practice: a shell command exiting with code ≥ 2.
Minimal repro — one assistant turn (= one parallel batch):
Bash: exit 2→ erroredBash: echo hello→Cancelled: parallel tool call Bash(exit 2) erroredRead: <any valid file>→Cancelled: parallel tool call Bash(exit 2) erroredControlled matrix (each row = one parallel batch; observing whether the innocent echo / Read siblings still run):
| Failing call in the batch | Exit code | Siblings (echo / slow bash / Read) |
|---|---|---|
|
false| 1 | all run ✅ ||
Readof a missing file | (read error) | all run ✅ — a failing Read does NOT cascade ||
exit 2| 2 | all cancelled ❌ ||
git statusin a non-repo dir | 128 | all cancelled ❌ ||
exit 128| 128 | all cancelled ❌ |Conclusions:
grepno-match,false,git diff).Read, regardless of how fast it returns.Readis collateral, the real trigger is always a sibling shell command.git status/git rev-parse/git -Crun in a directory that isn't a repo exits 128 and takes down every other call batched with it. The grep / git diff / ls whitelist doesn't cover these, nor arbitraryexit ≥ 2.Suggestion: make the "benign failure" handling exit-code aware (and/or treat side-effect-free / read-only siblings independently) instead of a fixed command-name whitelist. Independent read-only siblings (
Read, and shell commands with no side effects) shouldn't be cancelled by a sibling's non-zero exit.Adding mechanism detail from a controlled reproduction on v2.1.156 / macOS 26.5 / Opus 4.8 — current, well past the v2.1.122 "read-only command" fix.
Two findings I don't think are in the thread yet, both orthogonal to the exit-code trigger @aixleo66 documented above (i.e. independent of which error trips it).
1. The sweep is timing/completion-based — not positional, not strictly all-or-nothing
Calls in one turn dispatch incrementally in stream order. When one errors, the harness cancels every sibling not yet completed, reaching both directions:
finddispatched first). So it is not "everything after the error."So the operative model isn't "
Promise.allrejects → all cancelled"; it's "abort every in-flight or queued sibling at the instant of error."Whether a given sibling dies is a function of whether it had already finished (i.e. timing, appearing as a race condition).
2. The Auto-mode safety classifier is a genuine aggravator (clean A/B)
This bears directly on whether the v2.1.122 whitelist fix is sufficient; it isn't, because the bug's scope is a timing window, not a command name.
A borderline batch (
[echo, slow-find, slow-find, echo;whoami, <error-last>]) deterministically cancels the two middle calls under Auto but deterministically survives (repeated reps) underbypassPermissions.The only difference is the per-call classifier round-trip in Auto, which keeps the slow siblings in flight long enough to be caught by the trailing error. Remove the gate and the same calls finish first, so they survive.
Implication: a command-name/exit-code whitelist narrows what counts as a trigger, but any still-qualifying error (exit ≥2, a failed
WebFetch, a missing-fileRead, an MCP crash) will still sweep whatever siblings happen to be in-flight — and Auto mode widens that window.The complete fix is per-call result isolation (
Promise.allSettledsemantics: return each call's own result/error, cancel nothing), which several others have already identified in this thread.(Caveat on exit codes: not contradicting the exit-≥2 matrix above — the timing/mode behavior holds whatever the trigger.)
Confirming this on Claude Code 2.1.156, Opus 4.8, Windows 11. Same cascade: one failing call in a parallel batch cancels every sibling — including read-only
Read/Glob/Grepcalls to unrelated paths — withCancelled: parallel tool call ... errored.A Windows-specific ignition pattern worth noting: the environment banner declares
Shell: PowerShell, but the actual Bash tool runs git-bash (MINGW64) — verified vianode -e "process.env.MSYSTEM"→MINGW64,OSTYPE=msys. Following the banner, the model emits PowerShell syntax (2>$null,-Recurse) into git-bash, which fails withambiguous redirect/unknown option. That single failure then cascades and cancels the entire batch — often 15–30 read-only calls at once.Knock-on effect (relates to #63538): because the cancelled siblings return empty, the model misreads the batch as "tool output corruption / timeout" and, in a prior session, fabricated success reports for work that never ran. So the cascade isn't just wasted round-trips — it actively induces fabrication.
Notably, OpenAI Codex running in the same environment is unaffected, because it executes tools strictly sequentially — so the cascade cannot occur. The regression is specific to parallel-batch execution.
Repro is reliable: issue ~20 parallel tool calls where one uses a shell builtin that fails under git-bash.
Also reproducible on macOS — the cascade is cross-platform (this is currently tagged platform:linux).
Environment
Mechanism (reproduced 3× in a single session)
When the model emits several tool calls in one turn (a parallel batch), if any one call exits non-zero / throws, every other still-pending call in that same batch is cancelled,
regardless of whether they are independent. Each cancelled sibling returns:
Cancelled: parallel tool call <offending-call> errored
In my session the triggers were ordinary, recoverable errors: cat on a non-existent path (exit 1), a Python one-liner raising AttributeError (exit 1), json.load on an empty file (exit
1). Each single error wiped out 15–19 unrelated sibling calls (Read / Write / Edit / WebFetch / WebSearch / ToolSearch / MCP) — none of which ever executed.
Minimal repro — in one turn, issue a batch containing:
Result: every valid call returns Cancelled: parallel tool call Bash(cat …) errored and does not run.
Secondary symptoms in the same session (possibly same root cause — cross-refs #63859, #63797, #63538):
Impact — one trivial command error (a typo or wrong path) silently kills a whole batch of unrelated work; once the session degrades, subsequent batches keep failing, effectively
bricking the session until /clear or restart.
Expected — independent tool calls in a batch should each execute and return their own result (or own is_error). One sibling failing should not cancel unrelated siblings.
Workaround that holds — force one tool call per turn (no parallel batches), so there is no sibling to cancel.
the has become substantially worse thanks to opus 4.8. it just blew up hours of my time, and tokens, thinking it was working away when it had failedd tool calls. opus is decidedly convinced it's actually doing things, except a single fail is failing everything
i have to switch back to 4.7 because i cannot trust it not to batch and waste hours of my time and tokens
Can confirm: with the latest claude code update and opus 4.8, the model trips over this VERY hard.
The failure mode I see is that the model thinks it's environment is adverserial/poisoned/malicious in some way and goes down insane rabbit holes running weird commands.
Here's a snippet from it's thinking:
And, here's it describing after I asked what it was doing:
And, here's an entirely different session where I didn't get the thinking comment:
+1 — hitting this constantly. Adding a distinct repro and a downstream-impact note that may help triage.
Repro via a PreToolUse-hook block (not just a runtime error):
cd, blocked by acd-guard hook).cd …member alongside independent valid calls (git -C … status,grep,Read, an MCP call).cdmember — but all siblings are cancelled withCancelled: parallel tool call Bash(cd…) errored, including calls to unrelated files/tools.So the cascade fires on hook-blocked members, not only on runtime
Edit/old_stringfailures — same blast radius, additional trigger.Downstream impact worth linking — #63538: once a batch partial-cancels, the model frequently fabricates output for the cancelled/empty siblings (a phantom "commit landed"; a garbled re-render of a file that
wc -c/grep -cprove is intact on disk). In practice the cascade here (#22264) is the trigger and #63538 is the consequence — isolating independent siblings would remove most of that confabulation downstream.Expected: a hook-blocked or failed member should fail only itself; provably-independent siblings (different files, different tools) should still execute.
Environment: Claude Code 2.1.158, macOS, Opus 4.8.
I am also running into this error for the first time (started using Claude about 6 months ago). It seems to make the session agent VERY confused and start second guessing everything, rolling back changes (even when they actually landed as intended), hallucinating, etc. I asked Claude to run bash commands one at a time and it seemed to work fine from there, but obviously this is way slower.
same here after opus4.8 update
Confirming on Opus 4.8 + VSCode extension (macOS). Same harness cascade-cancel described here, now hitting independent Bash calls — and noticeably more frequent since switching to Opus 4.8.
Env: Claude Code v2.1.150, macOS 26.5 (arm64), VSCode extension, Opus 4.8 (
claude-opus-4-8). Opus 4.8 batches parallel Bash calls more aggressively than the previous model, so a single failing sibling takes down the whole batch much more often than before.Minimal repro (two independent Bash calls, one returns a routine non-zero exit):
Two unrelated Bash calls dispatched in the same turn:
Result:
Exit code 1(expected — grep found nothing)Cancelled: parallel tool call Bash(...) erroredCall 2 has zero dependency on Call 1 and would have succeeded on its own. A routine non-zero exit (
grepwith no match,diffwith differences,git diff --quiet, etc.) is enough to cancel every sibling in the batch.Expected: independent parallel calls should fail independently — return per-call results (
Promise.allSettledsemantics), not all-or-nothing.Consequence / workaround: I've had to add an explicit project rule forcing the model to run Bash sequentially (one call per message) and append
|| trueto commands with normal non-zero exits. That defeats the purpose of parallel dispatch and burns extra turns + context on serial retries.Same cascade on Windows, triggered by a chained command whose tail legitimately exits non-zero
Reproduced on Windows 11, Claude Code v2.1.158, Opus 4.8 (CLI). Adding two data points the existing reports don't cover: Windows, and a trigger class that isn't "Edit old_string not found."
The poison pill here is a compound command whose final segment exits non-zero even though the work succeeded:
... ; git grep -n 'pattern'→git grepreturns exit 1 when there are no matches (normal, not an error). The preceding output was produced fine, but the call is marked errored.git rm A B && echo ... && ls -la <dir>→git rmsucceeds and actually removes the files, but the trailinglshits the now-empty/removed directory → exit 2.In both cases a single non-zero exit cancelled every sibling tool call in the same parallel batch (
Cancelled: parallel tool call ... errored), including unrelated, fully-valid calls — e.g. aRead(KNOWLEDGE.md)immediately followed by itsEditwas cancelled because an unrelatedgit rmin the same batch exited 2. This wiped ~15 queued calls and forced re-issuing them one by one.Compounding it (overlaps #25773): the per-call result only shows the generic
... errored, so the real reason (a harmless exit 1 fromgit grep) is hidden — you can't tell a true failure from a no-match.Suggestion: independent tool calls should fail independently, and shell calls that exit non-zero without stderr (or known "0-or-1" tools like
git grep) shouldn't poison the whole batch.This issue was opened 2026-01-31.
So errors experienced the week of May 25 2026; are likely from a different root cause.
That's when they released Opus 4.8, and it has some sort of security feature where it tries to defend itself against prompt injection, and this error makes it think that it's in a hostile environment.
Corroborating — with evidence that the cancellation persists across turns, not only within the parallel batch.
On 2.1.156, after one Bash call errored (it used
/bin/grep, which doesn't exist on macOS — exit 127), the harness cancelled not just its in-batch siblings but calls the model issued in subsequent turns. Confirmed via theparentUuidchain: each cancelled call's parent is the prior cancel-result (a new turn), not the original batch. Cancelled calls included a state-changingmark_chapter(which then silently never happened) plus several reads.So a complete fix needs to (a) scope cancellation to the failing call only and (b) never carry cancel-state into later turns. Same behavior independently reported in #63576 (also 2.1.156).
Resolved as of v2.1.161 — confirmed fixed.
The official 2.1.161 changelog reads:
That lands exactly on the root cause from my earlier comment: the cascade was driven by a shell tool call's non-zero exit, and the v2.1.122 fix only whitelisted command names (grep / git diff / ls) — missing arbitrary
exit ≥ 2.Re-ran the same controlled repro on v2.1.162 / Windows 11 / Opus 4.8, one parallel batch with a non-whitelisted
exit 2culprit:Cancelled: parallel tool call … errored.Writein the same batch also completes. So it's the full "each tool returns its own result" fix, not a narrow read-only carve-out.Thanks for landing the exit-code-aware behavior 🙏 — this one can probably be closed.
Reproduces-as-fixed on v2.1.195 (macOS, Opus 4.8). Ran two controlled batches, each with a deliberately-erroring call plus siblings — including a deliberately slow sibling to catch any backward (in-flight) cancellation:
Bash culprit — batch of: fast
echo✅ ·exit 2❌ · ~3s busy-loop ✅ · fastecho✅→ only the
exit 2call errored; all three siblings (including the slow in-flight one) returned their own output. NoCancelled: parallel tool call … errored.Edit culprit (the originally-reported tool) — batch of: failing
Edit(old_string absent) ❌ · succeedingEditto a different file ✅ · ~3s busy-loop Bash ✅ · fastecho✅→ only the bad
Editerrored; the side-effectingEdit, the slow Bash, and the fast Bash all completed independently.So the exit-code-aware fix from v2.1.157 holds in both directions and across tool types (not Bash-only — failing
Editsiblings are independent too). Matches the earlier "confirmed fixed" reports. Looks safe to close. 🎯