Cancelling a parallel tool-call batch corrupts thinking blocks -> 400 "thinking blocks cannot be modified" permanently wedges the session
Problem
With extended thinking enabled, cancelling a batch of parallel tool calls (one tool errors → the rest are auto-cancelled) corrupts the thinking blocks of the in-flight assistant message. The next API request is rejected with a 400, and because the corrupted message stays in history, every subsequent turn re-sends it and re-fails — the session is permanently wedged.
Verbatim error (recurred on every turn afterward — including a plain text question and a slash-command invocation): ``API Error: 400 messages.1.content.54:thinkingorredacted_thinkingblocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.``
Verbatim transcript leading up to it — a batch of parallelBashtool calls, the first of which errored, cascading cancellations onto the rest: ``⏺ Bash(safari-browser screenshot /tmp/plaud_search.png --url plaud 2>/dev/null && echo "saved") ⎿ Error: Exit code 1 ... ⏺ Bash(safari-browser js "1+1" --url plaud ...) ⎿ Cancelled: parallel tool call Bash(safari-browser screenshot /tmp/plaud_sea…) errored ⏺ Bash(echo "PLAIN_TEXT_TEST_NO_BROWSER") ⎿ Cancelled: parallel tool call Bash(safari-browser screenshot /tmp/plaud_sea…) errored ⎿ API Error: 400 messages.1.content.54:thinkingorredacted_thinkingblocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.`` — Source: Claude Code session transcript, 2026-05-29
Type
bug
Steps to reproduce (best-effort)
- Extended thinking enabled (default in this session)
- Model:
claude-opus-4-7(1M context) - In one assistant turn, issue a batch of parallel tool calls (here: ~10
Bashcalls) - Arrange for the first call in the batch to error (here
safari-browser screenshot … 2>/dev/null→ Exit 1), so Claude Code auto-cancels the remaining parallel calls (Cancelled: parallel tool call … errored) - On the next API request, observe the 400
Expected
Cancelling parallel tool calls MUST preserve the assistant message's thinking / redacted_thinking blocks byte-for-byte (the API requires they stay identical when thinking + tool use is active), or discard the incomplete assistant turn cleanly. Either way the conversation should remain continuable.
Actual
A thinking block (here messages.1.content.54) is modified during the cancellation/reconstruction. The API rejects the request with 400. The bad message persists in conversation history, so every subsequent turn replays the same 400 — the user cannot even ask "what happened" or invoke a slash command. Only /clear or a new session recovers.
Impact
Total, unrecoverable session loss, triggered by a routine event (one tool in a parallel batch failing). High severity for any workflow that issues parallel tool calls with extended thinking on.
Environment
- Claude Code 2.1.153
- Model:
claude-opus-4-7(1M context), extended thinking enabled - Platform: macOS (Darwin 25.5.0)
8 Comments
I hit this same error message via a different repro. Not sure if I should log a separate issue since it essentially is just the tool call(s) being canceled. My repro:
Your context is now polluted and you will receive this same error about modifying thinking blocks.
Independent confirmation that this is structurally a sub-pattern of Cluster 13 (Extended-Thinking Session Wedging) rather than a standalone bug — and the parallel-tool-cancellation trigger surface for the same root failure mode documented at #63147.
Cluster framing
Tracking this as sub-pattern 13C in the four-axis cluster: extended thinking + one of four trigger conditions, all converging on
400 ... thinking blocks cannot be modified. The sub-patterns share one root failure mode (thinking-block serialization not surviving the round-trip), but reach the failure through different code paths:Field guide articulating all four sub-patterns: Extended-Thinking Session Wedging — A 36-Hour Surge with 4 Sub-Patterns and Operator-Side Recovery Paths (MIT, ~2,800 words).
What 13C uniquely surfaces
The cancellation path that triggers 13C is auto-cancellation (one tool errors, the harness cancels the rest) rather than the user-initiated cancellation that triggers 13B. The operator is not in the failure path — the harness pre-empts a batch in response to a sibling tool's error, and the in-flight assistant message's thinking blocks are corrupted as a side effect of the auto-cancel.
This is structurally important because it means the operator-side defense for 13C has to fire before the batch dispatches, not at the moment of cancellation. By the time
Cancelled: parallel tool call <tool> erroredappears, the corruption has already happened. The next turn returns the 400 and the session wedges.@craigts's variant repro (different code path, same end state) reinforces the framing: the trigger is "any cancellation event on an in-flight thinking-block-bearing assistant message during a parallel batch," not specifically the auto-cancel-on-sibling-error path. The serialization defect is upstream of which kind of cancellation fires.
Operator-side defenses (cc-safe-setup)
The hook shipped 2026-05-29 (PR #445, 54 tests passing) targets sub-pattern 13A at
SessionStartonly — it won't catch 13C, since 13C fires mid-session when a parallel batch lands.The 13C advisory is in design: a
PreToolUsehook on Bash batches that, when extended thinking is enabled and a parallel batch of 3+ tool calls is about to dispatch, surfaces a one-line stderr advisory recommending sequential execution instead. Rate-limited to one advisory per session. The cost of serializing 3 tool calls (a few seconds of additional latency) is small relative to the cost of a permanently wedged session. Targeting a June 2026 ship.Operator-side workaround in the meantime: when working in an extended-thinking session, dispatch tool calls sequentially rather than in parallel batches. The output is the same; the failure surface is narrower.
For sessions that have already wedged via this trigger, the post-hoc transcript repair tool
miteshashar/claude-code-thinking-blocks-fixis the recommended entry point — it operates on the on-disk transcript and produces a repaired version that can resume cleanly. The cluster's recovery surface is structurally narrow (hooks cannot reach the streaming-response handler where the corruption happens), so this tool is genuinely the best move when you're currently in a wedged session.What I'm watching for
The 13C trigger surface is specifically "auto-cancellation of parallel tool calls when one errors." That cancellation handler is presumably shared with other parallel-execution paths (MCP tool batches, subagent tool batches), so I expect to see structurally similar reports surface for those code paths as Cluster 13 gets more attention. I'm tracking the trigger-surface refinement in the cluster registry: cluster-tracker.html#cluster-extended-thinking-wedge.
Confirming 13C on Opus 4.8 (1M context) with two new trigger surfaces beyond the
safari-browserrepro — both are auto-cancellation of an in-flight parallel batch, exactly as framed above.Variant 1 — hung hook probes during a CLI workflow's startup. A dev-workflow orchestrator opened with a few hook-infrastructure probes (sourcing a shared shell helper, a writability check). Several returned no result (hung), so — impatiently — I dispatched another parallel batch mixing more
Bashprobes with severalTaskCreatecalls. OneBash(cd "$HOME/.claude/hooks" …) errored and cascadedCancelled: parallel tool call …onto the whole batch, including the non-BashTaskCreatecalls. Next turn:Session wedged; every subsequent turn (incl.
/exitattempts) replayed the 400. Notable: the cancelled batch was mixed tool types (Bash + TaskCreate), so the corruption is not Bash-specific — it's the parallel-batch cancellation path itself, consistent with @yurukusa's "upstream of which cancellation fires" point.Variant 2 — blocking
sleeppoll cancelled. Separately, aBash(sleep 120; gh pr view …)CI-poll running in a parallel batch was cancelled and produced the same 400 (×6 in a single session). Anything long-lived in the batch (asleep, a hung probe, arun_in_backgroundcompleting) widens the window where a sibling error can cancel mid-flight.Fleet-onset data point. Across three of my sessions the 400 first appears 2026-05-28 (×6 that day), then 05-29 (×1), 05-30 (×1) — and nearly every issue in this cluster was filed 2026-05-28. All of mine were already on
claude-opus-4-8, which lines up with #63412 ("Opus 4.7 unaffected"): the 4.8 rollout appears to be when signed thinking blocks became fragile under cancellation.alwaysThinkingEnabled: trueis the amplifier (guarantees every turn carries blocks to corrupt), not the cause.Operator mitigation that's holding for us (matches the "defense must fire before dispatch" framing): in extended-thinking sessions, never run blocking
sleep/poll loops in Bash (use background execution and poll one task at a time), and never dispatch a parallel batch you might cancel — serialize anything that can error. Same output, much narrower failure surface.claude-opus-4-81M context, extended thinking on@Benjamin-Sterrett — thank you for this. Both variants are exactly the kind of confirmation that closes the loop on 13C's mechanism, not just its existence.
The mixed-tool-type cancellation (Bash + TaskCreate) is the decisive piece. Until your Variant 1, the public repros were Bash-flavored (the
safari-browserminified output case, my earliersleep-loop case), which left open whether the corruption was tool-specific — e.g. a streaming-output edge case in Bash's pty handling that didn't survive the round-trip. Your case kills that hypothesis: aBash(cd …)error cancelling siblingTaskCreatecalls produces the same 400. That means the corruption sits squarely on the parallel-batch cancellation path itself, upstream of any per-tool serialization. The serializer is preserving block signatures across the cancellation, but the block content fragments are no longer the bytes those signatures were computed over → next turn, validator rejects.Variant 2 (
Bash(sleep 120; …)) is the latency amplifier I hadn't framed clearly. Anything that widens the window betweendispatchandall-completeincreases the probability a sibling errors mid-flight. That's why CI poll loops, hung hook probes, andrun_in_backgroundcompletions all show up disproportionately in 13C repros vs. snappy parallel batches. Worth pulling into the field guide as a separate "trigger amplifier" axis — the cancellation path is the root cause, but the time-in-flight of the batch is the exposure surface.Fleet-onset 2026-05-28 (×6 that day) on
claude-opus-4-8↑ #63412. This corroborates the version-inflection hypothesis that came out of #63412's "Opus 4.7 unaffected" thread: signed thinking blocks became fragile under cancellation specifically in the 4.8 rollout. Combined with the 13B / 13C / 13D filings all clustering on 2026-05-28, this is now strong enough to drop "intermittent" from the description of 13D — the variance there is amount-of-exposure, not random.I'm folding your mitigations into the field guide as the 13C explicit operator-side recovery surface (the cluster's defense had to fire pre-dispatch, exactly as you framed it):
run_in_background+ single-task polling has the same throughput with a much narrower failure window.The third one is the new addition from your Variant 1 — I had this only as folklore from
safari-browserreproductions. Your data makes it explicit.I'm treating 13C as structurally resolved at the operator level now (defense fires pre-dispatch, recovery path is
/exit+ serialization discipline). The remaining ambiguity sits in 13D (intermittent signed-block replay) where the trigger surface is still less crisp — if your Variant 2's blocking-poll cancellation produced 6 of the day's 400s on its own, that may be more of 13D's volume than the "intermittent" label suggests.Two follow-up questions if you have a minute:
TaskCreatecalls have any thinking blocks attached to them in the transcript, or was the corrupted block tied to the cancelledBash? (i.e. was the block the cancellation interrupted, or a sibling block elsewhere in the assistant message?)Either answer narrows the failure model further. Thank you again for taking the time to write this up at the level of detail you did — this is exactly the data that moves the cluster from "anecdote pile" to "characterized failure mode".
Confirming this still reproduces on Claude Code 2.1.158 (macOS) — the wedged transcript in our case was on 2.1.147, so it spans at least 2.1.147 → 2.1.158, not yet fixed.
Identical mechanism to the repro above: a batch of parallel
Bashtool calls, the first errored, the rest auto-cancelled (Cancelled: parallel tool call Bash(...) errored), then:Same permanent-wedge behavior: every subsequent turn (including a plain text question and a slash-command) re-sent the corrupted assistant message and re-failed. Only fix was to abandon the session and start fresh —
--resumere-fails immediately. Running tool calls sequentially or disabling extended thinking avoids re-triggering it.@kiki830621 — your transcript is the cleanest articulation I have seen of how two structural failure clusters compose into a single unrecoverable wedge. I have been tracking these as independent clusters; your reproduction is the bridge I was missing.
Cluster 20 — Parallel tool batch cancellation cascade — was articulated 2026-05-31 from three independent same-day reports (#64059 / #64052 / #64047). Failure mode: any single non-fatal tool error cancels every sibling call in the parallel batch with
Cancelled: parallel tool call X errored. Two axes — the cascade behavior itself, and the cancellation message reading identically to a user interrupt (so the model misattributes the cascade to user action).Cluster 13 — Extended Thinking wedge — has been accumulating since mid-May. The lead issue is #63147 (40 reactions) and at least 10 duplicate-or-related filings within a 72-hour window from 2026-05-28: #63072, #63078, #63121, #63199, #63231, #63239, #63335, #63337, #63341, #63346, #63463. Failure mode:
400 messages.N.content.M: thinking or redacted_thinking blocks in the latest assistant message cannot be modified, every subsequent turn re-fails because the corrupted message stays in history.Your issue is the cross-cluster bridge: the cluster 20 cascade is one of the things that corrupts the thinking blocks that then trigger the cluster 13 wedge. Until now I had read these as two independent failure modes; your transcript shows them composing.
Cluster 20 in isolation wastes tokens (40-100K per cascade event, ~$1.50-$7.50 at Opus output rates) but the session can usually continue — the operator just sees the cascade and adjusts.
Cluster 13 in isolation has a known operator workaround:
/clearto discard the corrupted session, accepting the context loss.The composition is unrecoverable in a different way. When the cascade event itself causes the thinking-block corruption, the operator hits the cluster 13 wedge with no prior signal that the cluster 20 cascade was the trigger. The natural debugging response — "why is my session wedged? I will look at the last few turns" — does not surface the cascade as the cause, because the cascade is in a previous turn that completed normally except for the silent corruption. Operators end up either re-clearing repeatedly (and learning to fear extended thinking + parallel calls) or blaming the wrong subsystem.
Stitching the cluster-20 mechanics into the cluster-13 corruption signature from #63147:
thinkingblocks + a parallel batch of tool callsthinkingblocks that have emptythinkingtext but retainedsignature(the on-disk shape jdrolls documented in #63147)400 thinking/redacted_thinking blocks cannot be modifiedThe cluster 20 cascade is one corruption trigger; cluster 13 documents at least one other (session resumption). Both produce the same on-disk shape because both interrupt the thinking-block lifecycle at the same point.
For operators running extended thinking with parallel tool calls, the cluster 20 mitigations are necessary but not sufficient — they reduce cascade frequency, which reduces this wedge's frequency, but a single cascade with extended thinking enabled is enough to wedge a session permanently.
git/curl/pkill(the three most-cited cluster 20 cascade triggers) in parallel batches when extended thinking is enabled — even one mistake there can wedge the session for the rest of the day..jsonl..jsonlto re-shape the corrupted block) is operator-side but invasive. The conservative path is/clearand accept context loss.Two cluster-20 defense hooks shipped 2026-05-31 in cc-safe-setup (MIT, ~1,500 unique clones / 14 days):
parallel-cascade-detector.sh(PR #501, PostToolUse) — countsCancelled: parallel tool call X erroredin a rolling window and warns when threshold crossed. Surfaces the cascade after it has happened.parallel-batch-size-limiter.sh(PR #503, PreToolUse) — counts parallel tool calls in a 500ms rolling window and warns at batch-size threshold. Surfaces the size signal before any failure can cascade.Neither hook prevents the cluster 13 corruption — they reduce cluster 20 cascade frequency. The cluster 13 wedge is an upstream-side fix: the server needs to either (a) reject empty-text-but-signed thinking blocks from being persisted in the first place, or (b) tolerate them on re-send rather than rejecting with 400.
For cluster 20 (cascade behavior): change the default from "fan-out, abort on first failure" to "fan-out, continue on partial failure". The right semantic for parallel reads/probes; the existing semantic optimizes for transactional batches, which are the rare case.
For cluster 20 axis 2 (indistinguishability): distinguish system cancellation from user cancellation in the result message (the minimum fix proposed in #64047).
For cluster 13 (thinking block wedge): persistence-layer change — either reject the corrupted shape at write time, or tolerate it at re-send time. The current behavior persists invalid-on-read state, making every subsequent turn re-fail.
Your transcript is going into my cross-cluster reference set; it is the cleanest articulation I have for the cluster 20 → cluster 13 composition. Filing your report at the time you did (rather than waiting for a separate model-behavior issue) was the right call — the composition is the actionable insight.
**Same root cause on Linux, triggered by a user-injected prompt mid-batch (not a tool-error cascade).**
Env: Claude Code 2.1.122 embedded in Claude Desktop 1.9255.2, official API (1p), Linux.
In my session the corruption was triggered not by a tool erroring, but by sending a normal message while a parallel tool batch + interleaved thinking was still in flight. Transcript event order:
Every retry afterward (
continue, a new prompt, even asking about the error) re-sent the frozen turn and re-failed with the identical 400 → session permanently wedged; only a fresh session recovered.So the trigger generalizes beyond auto-cancel-on-error: any interruption of an in-flight interleaved-thinking + parallel-tool assistant turn corrupts
content.Mand wedges the session — here it was just a normal user prompt arriving while the parallel tool batch was still executing.A mesma coisa acontece excessivamente comigo. EXCESSIVAMENTE.