Parallel tool calls: one benign tool error cancels all sibling calls → agent misreads as broken shell and flails

Resolved 💬 3 comments Opened May 31, 2026 by jddeitch Closed Jul 3, 2026

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/pwd probes, 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)

  1. Don't cancel siblings on a benign non-zero exit — let each parallel Bash call resolve independently; a grep exit-1 in call A shouldn't abort call B.
  2. 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.)
  3. Expose disable_parallel_tool_use as a Claude Code setting / env var (it exists on the API tool_choice but isn't surfaced in Claude Code).
  4. 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

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗