Parallel tool calls: one benign tool error cancels all sibling calls → agent misreads as broken shell and flails
Summary
When an assistant turn emits multiple parallel tool calls and any single call returns an error, Claude Code cancels all still-pending sibling calls in that batch. Each cancelled call returns a tool result whose text is Cancelled: parallel tool call Bash(<X>) errored. The triggering error is frequently benign (a grep that matched nothing → exit 1, a Read of a missing path, a TaskCreate with a missing field). Because trivial commands like echo hello and pwd then come back marked as errors, the model misreads it as a broken shell and spirals into diagnostic probing — and since it issues the probes in batches too, they get cancelled by the next failing sibling, producing a self-reinforcing flailing loop that burns large amounts of time and tokens for zero progress.
Evidence (from a real session)
A single assistant turn issued 27 parallel tool calls. One was echo "=== plans dir ===" && ls -la docs/plans/ | grep -i "..." — the grep matched nothing and exited 1. That one benign exit caused 26 sibling calls to be cancelled, including pwd, echo hello, and printf 'literal-output-test\n'; true (which cannot exit non-zero). All 26 returned:
<tool_use_error>Cancelled: parallel tool call Bash(echo "=== plans dir ===" && ls -la docs/…) errored</tool_use_error>
Across the session, 31 of 36 total tool errors were this cancellation cascade, seeded by only ~5 genuine (and benign) errors. Grouping tool_use by message.id confirmed the trigger is a genuine sibling error, not batch size or any specific tool: a 9-call batch with one error cancelled 5 siblings; an 18-call batch with zero errors cancelled nothing.
Impact
- Severe, silent token/time waste. The model cannot distinguish a cancelled sibling from a real failure, concludes the environment is broken, and flails (re-running commands, emitting
echo/pwdprobes, defensively duplicating commands within a batch). Sessions can burn dozens of tool calls and hundreds of thousands of tokens recovering. - Misdiagnosis. The behavior looks like model degradation or a broken shell; it is neither. It took an extended investigation to trace it to this mechanism.
- Amplified under auto-approval / per-call permission classification, which serializes a batch and keeps siblings pending longer (wider cancellation window).
Expected behavior
A benign non-zero exit in one parallel call should not silently cancel unrelated sibling calls; or, if cancellation is intended, the cancellation result should be worded so it is unmistakably not an environment failure and instructs re-running the single call — so the model recovers instead of flailing.
Actual behavior
Sibling calls are cancelled and surfaced with Cancelled: parallel tool call X errored, which reads to the model as a catastrophic/broken-shell signal.
Suggested fixes (any one would help)
- Don't cancel siblings on a benign non-zero exit — let each parallel Bash call resolve independently; a
grepexit-1 in call A shouldn't abort call B. - Reword the cancellation result to explicitly say a sibling errored, the environment is fine, and the call can simply be re-run alone. (Cheapest fix; directly stops the misinterpretation.)
- Expose
disable_parallel_tool_useas a Claude Code setting / env var (it exists on the APItool_choicebut isn't surfaced in Claude Code). - Fire a hook event for cancelled calls so users can build their own corrective context injection (PostToolUse currently does not fire for cancelled calls and does not receive result text, so this can't be mitigated via hooks today).
Environment
- Claude Code: 2.1.159
- Model: claude-opus-4-8 (1M context)
- OS: macOS (darwin 25.4.0)
- Mode: auto-approval (skipAutoPermissionPrompt) with PreToolUse hooks present
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗