Extended thinking: signed thinking block 'cannot be modified' (400) permanently wedges session

Open 💬 15 comments Opened May 28, 2026 by dammahs

Summary

With extended/interleaved thinking enabled, Claude Code intermittently replays a modified signed thinking block in the latest assistant message, producing:

API Error: 400 messages.N.content.M: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.

Once this occurs the corrupted assistant message is persisted in the transcript, so every subsequent request re-sends it and fails identically. The session becomes permanently unrecoverable — re-prompting and "try again" both reproduce the same 400. A full /clear is the only reliable escape (/rewind to before the failed turn may also work).

Environment

  • Reproduced across Claude Code 2.1.143, 2.1.150, 2.1.153 (currently on 2.1.154).
  • macOS (darwin 25.5.0).
  • Extended/interleaved thinking enabled.

Impact

Silent, repeated total loss of a working session on long-running tasks. Especially costly on Opus, where extended thinking is core to the product — the only durable user-side workaround is disabling thinking, which is a capability downgrade users shouldn't have to accept.

Evidence

Observed in 5 separate session transcripts. The error references a thinking block (the cited content.M is always a thinking block), and the same fingerprint recurs:

  • messages.19.content.21 (recurs across multiple sessions)
  • messages.1.content.7
  • messages.327.content.14

(messages.N.content.M indexes the API request payload, not any project file.) The 400 is raised before model reasoning starts, so any "self-analysis / retry" inside the same session just resends the invalid transcript and fails again.

Reproduction

Trigger: a tool-heavy assistant turn while extended thinking is on, where the harness reconstructs that assistant message after it was first emitted. Observed concrete triggers:

  1. Background tasks — a run_in_background Bash command whose async completion notification re-enters the originating assistant turn; on resume the harness replays that turn's thinking block modified → 400.
  2. Parallel tool-call cancellation — a batch of parallel tool calls where one errors and cancels its siblings (Cancelled: parallel tool call ... errored); the next turn 400s on the thinking block.
  3. Plain tool-heavy turn — in one session the 400 fired immediately after a single file-write tool call on a turn that contained thinking blocks. So the trigger is more general than (1)/(2).

Expected behavior

Signed thinking / redacted_thinking blocks must be persisted and replayed byte-for-byte unmodified across all harness operations — including background-task resumption, tool-call cancellation, and normal multi-tool turns. The client should never emit a modified thinking block.

Actual behavior

The harness emits a modified thinking block on the latest assistant message, the API rejects it with a 400, and the session is permanently wedged with no in-session recovery.

View original on GitHub ↗

15 Comments

pngwn · 1 month ago

I am seeing the same issue. I submitted via /feedback, details below so you can link them / find them from this issue.

---

Environment Info

  • Platform: darwin
  • Terminal: WarpTerminal
  • Version: 2.1.154
  • Feedback ID: 65b0d212-a8ef-4cc5-bdc7-605b5aa488e7

Errors

[{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"messages.3.content.11: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.\"},\"request_id\":\"req_011CbVYUq4SywdqLvZvKdkfj\"}\n    at generate (/$bunfs/root/src/entrypoints/cli.js:11:63816)\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:7694)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-28T19:47:02.198Z"},{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"messages.3.content.11: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.\"},\"request_id\":\"req_011CbVYVQsBZi4KCetdnrzvz\"}\n    at generate (/$bunfs/root/src/entrypoints/cli.js:11:63816)\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:7694)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-28T19:47:10.117Z"},{"error":"Error: Error during compaction: API Error: 400 messages.3.content.11: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.\n    at Vn3 (/$bunfs/root/src/entrypoints/cli.js:7737:1645)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-28T19:47:10.119Z"}]
braklo · 1 month ago

Additional data point confirming this is not macOS- or model-specific.

Environment

  • Claude Code 2.1.154 (native install)
  • Model: claude-opus-4-8 (existing reports here are opus-4-7 — so the bug persists into 4.8)
  • Platform: Linux (openSUSE Tumbleweed) — all prior reports in this thread are macOS, so the root cause is platform-independent
  • Extended/interleaved thinking enabled (default)

Trigger (matches #63231)
A single long assistant turn (one requestId, 37 content blocks) with thinking interleaved between many tool_use blocks, ending in parallel tool calls:

thinking, text, (thinking, tool_use)×~16, tool_use, tool_use, tool_use, thinking

Tools in that turn: 12×Bash, 3×Read, 2×Edit, 1×Write, 1×AskUserQuestion. So AskUserQuestion inside a large interleaved-thinking turn reproduces it here too (cf. #63231).

Error progression in the same session

API Error: 400 messages.3.content.15: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified...
API Error: 400 messages.7.content.53: ...

The content.N index grows with turn length, consistent with longer interleaved turns being more likely to hit it.

On-disk vs in-flight finding (may help narrow the cause)
In the persisted transcript (~/.claude/projects/.../<session>.jsonl), every thinking block is stored with a valid signature but an empty thinking string ("") — this is true for all my sessions, not just the broken one, so it appears to be normal persistence behavior (text dropped, signature kept).

I tried editing the on-disk transcript to strip those empty thinking records (rewiring parentUuid); the session resumed and worked for short turns, but the 400 returned on the next long interleaved-thinking turn. So the corruption is in the in-flight/in-memory replay of the latest assistant turn, not in the persisted file — editing the transcript does not prevent recurrence. Confirms the "wedge" is regenerated live whenever a sufficiently long interleaved turn occurs.

User-side workaround that holds: keep turns short (few tool calls each) so no single assistant turn accumulates a long interleaved thinking+tool sequence. /clear / new session escapes a wedged state, as already noted.

jonathan-chamberlin · 1 month ago

I've been seeing the same issue.

BlackHawk2026 · 1 month ago

我也遇到这个问题了 macos系统中的vscode软件中的claude code插件,版本 2.1.154,一直回复API Error: 400 Failed to deserialize the JSON body into the target type: messages[3].role: unknown variant system, expected user or assistant at line 1 column 80437

yurukusa · 1 month ago

Independent confirmation that this is structurally sub-pattern 13D of Cluster 13 (Extended-Thinking Session Wedging) — and the most operationally serious of the four sub-patterns because the trigger surface is not pinned.

Cluster framing

The four sub-patterns share one root failure mode (thinking-block serialization not surviving the round-trip between the client and the API), but reach the failure through different code paths:

  • 13A — resume serialization corruption (#63147, 33 reactions, canonical root-cause analysis by @jdrolls)
  • 13B — cancel-during-AskUserQuestion poisoning (#63143)
  • 13C — parallel-tool-batch cancellation corruption (#63192)
  • 13D — intermittent signed-thinking-block replay (this issue + the 10+ duplicate-flagged reports)

Field guide articulating all four sub-patterns with the recovery surface limits and the operator-side advisory hooks: Extended-Thinking Session Wedging — A 36-Hour Surge with 4 Sub-Patterns and Operator-Side Recovery Paths (MIT, ~2,800 words).

Why 13D is the largest by volume and the hardest to defend against

The reaction count on this specific issue is comparable to 13B and 13C (~6), but the duplicate-flagged cluster around it is the largest of the four sub-patterns. Aggregating this issue with #63199, #63072, #63231, #63341, #63121, #63078, #63346, #63239, #63337, #63247, #63213 gives at least 12 reports — likely 50+ reactions in total — describing the same failure without a 13A/B/C trigger.

@braklo's note that it reproduces on claude-opus-4-8 (not just 4-7), and the version-independent reproduction across v2.1.143, v2.1.150, v2.1.153, v2.1.154 documented across this thread, place 13D outside the model and outside any single release. The trigger surface is upstream of release boundaries — which means an operator-side advisory hook can warn but cannot block, since the failure can fire on any extended-thinking session regardless of the session's history.

The reporter's articulation — "the only durable user-side workaround is disabling thinking" — is the honest summary. Disabling extended thinking trades capability for session durability. There is no fourth path that keeps both.

Possible additional trigger surface (13E candidate)

One forum post I found during the research surfaced a fifth candidate sub-pattern not yet pinned to a GitHub issue: "When the text contains unpaired Unicode surrogates (which can occur when Claude reasons about binary data, code, or malformed strings), the surrogates are stripped, the text changes, and the cryptographic signature becomes invalid." If this trigger is real, it would explain some of the intermittency observed in 13D — sessions that reason about binary content, source code with unusual Unicode, or malformed strings would be more susceptible. I'm flagging it for visibility; it would benefit from a reporter who can reproduce on a session that hits binary/code-heavy reasoning.

Operator-side defenses (cc-safe-setup)

The hook shipped 2026-05-29 (PR #445, 54 tests passing) targets sub-pattern 13A at SessionStart. The advisory for 13D is in design as a PostToolUse hook that scans the trailing transcript for the precursor shape (thinking blocks with empty text but non-empty signature) mid-session, ahead of the next resume — complementing the SessionStart hook by catching the shape before resume actually fires the 400. Rate-limited per session. Targeting a June 2026 ship.

The honest limit on the 13D advisory: since the trigger is not pinned, the advisory has to fire on "extended thinking is active and the historical reproduction rate suggests caution," which is broader than the 13A/B/C advisories. Operators who want the strongest 13D defense should disable extended thinking for tasks that don't strictly require it, and accept the capability downgrade.

For sessions that have already wedged via any sub-pattern, miteshashar/claude-code-thinking-blocks-fix is 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 at the prevention layer; the repair layer is where the practical workflow recovers.

What I'm watching for

The fastest signal for 13D would be a reproducer that's deterministic enough to pin the trigger surface — currently the 12+ reports describe symptoms but not preconditions. If a reporter can isolate the trigger to a specific kind of content (binary reasoning, certain Unicode ranges, specific tool sequences), the advisory layer can target it specifically rather than coarsely. I'm tracking the trigger-surface refinement in the cluster registry: cluster-tracker.html#cluster-extended-thinking-wedge.

Grimicus · 1 month ago

Reproduces on 2.1.156, Windows 11, resuming an old multi-compaction session with --model claude-opus-4-8 --resume

Hitting the same bug. Adding a specific trigger combination not yet documented here:

Trigger sequence:

  1. Old long-running session, multiple auto-compactions over its lifetime, originally created on an earlier Claude Code version
  2. Updated Claude Code to 2.1.156
  3. Resumed with model switch in a single command: claude --model claude-opus-4-8 --resume <session>
  4. Agent produces a tool-heavy turn with interleaved thinking (~39 content blocks; thinking blocks at indices 36 and 38)
  5. Next request (tool results returned) → 400 on messages.5.content.36 / messages.5.content.38

Environment:

  • Claude Code 2.1.156
  • Windows 11 Pro (10.0.26200), PowerShell 7
  • Model: claude-opus-4-8, extended/interleaved thinking ON

Confirmed sticky behavior: rewinding to just before the failure re-sends the same corrupted assistant turn → identical 400. Only recovery was rewinding to before the entire offending assistant turn. Then the same 400 returned again on the next large interleaved-thinking turn.

Full local .jsonl transcript available with sanitized excerpts on request.

tlmader · 1 month ago

Additional data point: same 400 in non-interactive Agent SDK mode (no /plan, /clear, /compact, or model-switch)

Reproduced this exact failure in a headless CI environment, which narrows the trigger: it is not specific to interactive context operations. The harness here is the official GitHub Action wrapping the bundled @anthropic-ai/claude-agent-sdk — a single non-interactive agent run, started fresh, with no compaction/clear/model-switch and no plan mode.

Error (identical signature):

API Error: 400 messages.1.content.28: `thinking` or `redacted_thinking` blocks in the latest
assistant message cannot be modified. These blocks must remain as they were in the original response.
  at readMessages (.../@anthropic-ai/claude-agent-sdk/sdk.mjs)
  • request_id: req_011CbYAd4LaUy9vhzyrkCTKN
  • Model: claude-opus-4-8, Linux x64 runner, extended/interleaved thinking enabled, single uninterrupted agent loop (no /clear, /compact, /model, or /plan — none of those concepts exist in this non-interactive path).

Differential evidence (most useful part): the same harness, same model, same system prompt was run against two different inputs:

| | run A (passes) | run B (fails) |
|---|---|---|
| is_error | false | true |
| num_turns | 11 | 39 |

The failure correlates with transcript length / tool-call density (run B does many more tool-heavy assistant turns), pushing the first assistant message past ~28 content blocks. The 400 then fires from readMessages while reconstructing that long transcript for the next request — block 28 is a thinking block that gets modified during reconstruction. A short transcript (11 turns) never reaches the buggy path.

This suggests the root cause lives in the SDK's transcript replay/serialization (readMessages) and is reachable on any sufficiently long tool-heavy turn — independent of /plan, compaction, or model switching.

yurukusa · 1 month ago

@tlmader — The non-interactive Agent SDK confirmation is the most important data point this thread has added on the trigger surface so far. It rules out the entire "interactive operation" hypothesis that the early thread (myself included) had been working with — /plan, /clear, /compact, model-switch were red herrings. The trigger is the round-trip of a thinking-block signature through any path that touches transcript reconstruction.
This adds a fifth sub-pattern to the four I documented earlier:

  • Failure surface: Headless invocations of @anthropic-ai/claude-agent-sdk (including the official claude-code-action GitHub Action wrapper) hit the same 400 "thinking blocks cannot be modified" when the transcript reconstructed for a new tool round-trip references a signature whose accompanying text is empty.
  • Distinction from 13A-D: No operator-controllable intervention point. There is no /rewind, no /compact, no session to abandon — the SDK either succeeds the round-trip or fails permanently for that invocation. CI pipelines can retry the whole job, but the same input reproduces the same failure.
  • Operator-side mitigation gap: matcher:"" PreToolUse hook on the calling side cannot reach the SDK's internal serialization layer. The only operator-side workaround I can see is to short-circuit the SDK invocation with retries that downgrade the model to a non-extended-thinking config (e.g., --model claude-sonnet-4-6 instead of claude-opus-4-8) — but this loses the entire extended-thinking value the harness was paying for.

This makes 13E the most operationally serious of the five sub-patterns: there is no operator-side recovery without sacrificing the feature you're invoking the SDK for. The implication for the upstream fix is that it has to live in the SDK's transcript reconstruction layer, not in any one of the interactive entry points. A fix that only patches /compact or the auto-rebuild path inside the CLI will leave the SDK surface unaddressed, and CI pipelines will continue to hit this in production.
For CI environments hitting this today, the least-bad mitigation I've seen is a wrapper around the SDK invocation that catches the 400 with the specific "thinking blocks cannot be modified" body and re-invokes without thinking_config set — accepts the productivity loss to keep the pipeline green. Far from ideal but it converts a permanent wedge into a degraded-quality completion.

yurukusa · 1 month ago

@tlmader and thread — Correction on my earlier reply: I labeled the non-interactive Agent SDK surface "sub-pattern 13E", but my own prior tracking had already assigned 13E to the ToolSearch trigger surface (a different mechanism documented at https://github.com/anthropics/claude-code/issues/63147#issuecomment-4580358273). To avoid number collision with the existing nomenclature, I'm renaming the SDK/CI surface to sub-pattern 13H — Non-interactive SDK / CI surface.
The substance of the analysis stands; only the label changes. Corrected sub-pattern map (as of 2026-05-30):

  • 13A — Auto-compact resume trigger (hook shipped: extended-thinking-resume-warning, PR #445)
  • 13B-D — described in the 4-sub-pattern field guide
  • 13E — ToolSearch trigger surface (separate mechanism, prior articulation)
  • 13F — v2.1.154 context-ops trigger
  • 13G — Opus 4.7 → 4.8 mid-conversation swap (hook shipped: opus48-thinking-wedge-advisor)
  • 13H — Non-interactive SDK / CI (today's articulation, this thread)

Apologies for the number churn. The 13H analysis itself is unchanged: there is no operator-side recovery without sacrificing the extended-thinking feature the harness was invoking, and the upstream fix has to be in the SDK's transcript reconstruction layer — not in any of the interactive entry points where the existing fixes for 13A-D land. CI pipelines hitting the SDK surface will not be addressed by a /compact patch or the auto-rebuild path inside the CLI.
The 7-pattern split (13A through 13H, with 13E ToolSearch occupying the slot I incorrectly reused) is being tracked at cluster-tracker.html for the cc-safe-setup repo. The current shipped-hook coverage is 13A and 13G; 13B-D have advisory hooks in design, and 13E/F/H have no operator-side intervention surface yet beyond the workarounds already documented in this thread.

kelvinwop · 1 month ago

it doesn't permanently wedge my session but running into it is annoying as fuck

dankefox · 1 month ago

Adding a sanitized data point that looks related, but presents as a silent/malformed-tool stall rather than always surfacing the 400 directly.

Environment

  • Claude Code: 2.1.158
  • Platform: macOS 26.4.1 / Darwin 25.4.0 / arm64
  • Model: claude-opus-4-8[1m]
  • Effort: reproduced with high; risk appears higher with xhigh / max
  • Session type: long interactive/resumed Claude Code session with normal tools enabled

Observed behavior

In a resumed interactive session, a natural user request that combines brief reasoning + tool use + final text can produce this sequence:

  1. Assistant message contains thinking and stop_reason=tool_use.
  2. No valid tool_use block is emitted for Claude Code to execute.
  3. Claude Code injects the recovery prompt: Your tool call was malformed and could not be parsed. Please retry.
  4. The session may keep thinking and never produce final visible text.
  5. The client/app has to interrupt or abandon the turn.

So the user-visible failure can look like an empty reply / stuck thinking turn even when no 400 is shown in the UI.

A/B notes

  • Clean claude -p tests with Opus 4.8 high can succeed for short replies and Bash tool use, so this does not look like a general API outage.
  • DISABLE_INTERLEAVED_THINKING=1 was not sufficient in the interactive/resumed route.
  • Running the same project-like tests with --thinking disabled avoided the malformed-tool path and produced valid Bash tool_use + final text, but that is a capability downgrade.
  • Stable fallback is claude-opus-4-6[1m] with max effort.

This seems consistent with the existing reports around extended/interleaved thinking + tool use corrupting or wedging the latest assistant turn. I can provide more sanitized transcript structure if useful, but cannot post private chat content publicly.

m13v · 1 month ago

the part that bites isn't the thinking text, it's the signature. the server signs the block over its exact serialized bytes, so the client has to replay it verbatim. the moment a harness round-trips it through its own typed message model (parse to a struct, drop a null field, reorder keys on re-serialize) the bytes drift and the signature stops validating, even though the visible content is identical. background-task resume and parallel-cancel both force that re-serialize on the latest assistant turn, which is why those two repro most. fix is to stash the raw block keyed by id and splice it back unmodified, never rebuild it from parsed fields.

yurukusa · 1 month ago

@m13v — this is the cleanest articulation of the root mechanism this thread has produced. The signature is over exact serialized bytes, not over content the harness sees. Frame it that way and the whole sub-pattern map collapses to one invariant violation, with each documented sub-pattern just a different trigger path forcing re-serialization on the latest assistant turn.

Updating the Cluster 13 articulation against your framing:

Root mechanism (single, shared across 13A–D and 13H)

  • Server signs each thinking block over its emission-time canonical byte sequence.
  • Signature validates iff the client replays those exact bytes.
  • Any path that parses to a typed struct and re-serializes (drop null/optional fields, reorder keys, normalize whitespace, canonicalize escapes) produces a content-identical, signature-invalid block.

Why each sub-pattern reaches it

The five established sub-patterns all force a parse → struct → re-serialize round-trip on the latest assistant turn, but through different code paths:

  • 13A — auto-compact resume (#63147): the resume path rebuilds the latest turn from the parsed transcript; the round-trip is the re-serialize step. Matches your "background-task resume forces that re-serialize" framing directly.
  • 13B — cancel-during-AskUserQuestion (#63143): the interactive cancel rewrites the assistant turn to inject cancellation state.
  • 13C — parallel-tool-batch cancellation (#63192): cascading tool-call cancellation rebuilds the latest turn's content array to drop unfulfilled tool_use blocks; surrounding thinking blocks get re-serialized as a side effect even though they are not the target of the edit. Matches your "parallel-cancel forces that re-serialize" framing directly — the cancel target is the tool_use, but the byte drift lands on the adjacent thinking.
  • 13D — intermittent signed-block replay (this issue): same root, triggered without any of the above edit operations — the SDK's normal request-loop round-trip is sufficient, which matches why 13D shows up at version boundaries (v2.1.143 → v2.1.154 → v2.1.156 → v2.1.158) where serializer behavior shifted.
  • 13H — non-interactive Agent SDK / CI (@tlmader above): platform-independent confirmation that the trigger surface is the SDK's own request loop, not any interactive operation.

Fix shape, restated against your framing

stash the raw block keyed by id and splice it back unmodified, never rebuild it from parsed fields

This is the only fix that survives all sub-patterns at once because it sidesteps the byte-drift class entirely:

  1. At message-receive, capture each thinking block's raw bytes alongside its block id (or position index if no id).
  2. Treat the typed in-memory representation as read-only — for display/logging, never for re-emission.
  3. On the next request, splice the original bytes back at the same position before serialization.
  4. Verify with a unit test asserting bytes(block_after_roundtrip) == bytes(block_at_creation) keyed by id, across the entire transcript-reconstruction surface (resume, cancel, parallel-batch unwind, SDK request loop).

The current implementation appears to rely on JSON-level identity-by-content, which is exactly the assumption byte-identity breaks.

Implication for harness designers (anyone building on @anthropic-ai/claude-agent-sdk or the Anthropic SDK directly)

If you build any wrapper that observes or transforms assistant messages, treat thinking blocks as opaque, byte-stable, identity-keyed — pass-through only. Any parse/re-serialize layer above the SDK reintroduces the same hazard the SDK is trying to avoid; byte stability has to be end-to-end.

This is also the honest framing for operator-side mitigations. The cc-safe-setup hook for 13A (extended-thinking-resume-warning, PR yurukusa/cc-safe-setup#445) detects the trigger surface but cannot fix 13D/13H, because the trigger there is the SDK's own request loop. The only universally reliable operator-side mitigation across all sub-patterns remains claude --model claude-opus-4-7 (which avoids signed thinking blocks entirely) for sessions where reliability matters more than reasoning depth.

Updating linked artifacts to credit your framing:

  • Cluster 13 field guide (gist 8c6be069) — adding a "Root mechanism" preamble framing all sub-patterns as byte-identity violations of a single invariant, citing this comment.
  • cluster-tracker.html Cluster 13 entry — same revision, byte-identity framing as the lede.

Thanks for the depth — this is the kind of structural framing that lets the cluster argument actually converge on a single fix instead of one mitigation per sub-pattern.

dankefox · 1 month ago

@m13v @yurukusa thanks, the byte-level signature explanation makes sense and matches what we observed.

Adding our sanitized launch details in case they help narrow which Claude Code path is re-serializing the latest assistant turn:

PM2 app/server -> tmux session -> claude CLI interactive/resume session

claude --dangerously-skip-permissions   --resume <session-id>   --model claude-opus-4-8[1m]   --effort high   --max-thinking-tokens 8000

Notes:

  • This is a long-running resumed interactive session, not claude -p.
  • Our wrapper does not edit Claude Code's jsonl transcript or rebuild thinking blocks. It only drives the CLI and watches the transcript/output.
  • The failure we saw was: assistant message had thinking and stop_reason=tool_use, but no valid tool_use block appeared for the harness to execute. Claude Code then injected Your tool call was malformed and could not be parsed. Please retry., and the turn could remain in thinking with no final visible text.
  • Clean claude -p tests with the same model could pass; the issue showed up in the interactive/resumed route.
  • DISABLE_INTERLEAVED_THINKING=1 was not sufficient for our route.
  • --thinking disabled made the same project-like tool tests produce valid tool calls and final text, but it removes the signed-thinking surface entirely, so it is only a degraded workaround.

Questions:

  1. Does the --resume path for a long interactive session force a parse -> typed model -> re-serialize round trip on the latest assistant message, even if the app wrapper never edits the transcript?
  2. Is --thinking disabled currently the only reliable workaround for this class of signed-thinking replay issue, or should DISABLE_INTERLEAVED_THINKING=1 be expected to avoid it if applied correctly?
  3. Would maintainers need sanitized evidence beyond block structure (thinking, stop_reason=tool_use, missing tool_use, recovery prompt, timing), or is that enough to confirm this is the same raw signed-block replay problem?
yurukusa · 1 month ago

@dankefox — the launch details are exactly what narrows it. I maintain cc-safe-setup (community side, not Anthropic), so treat Q1/Q3 as evidence-based inference rather than a maintainer confirmation — but your data answers most of it on its own.
Q1 — does --resume force a parse → typed-model → re-serialize round-trip even though your wrapper never edits the transcript? Your own A/B is the discriminating evidence: clean claude -p passes, the long-running --resume route fails, same model. Since your wrapper only drives the CLI and watches output (never rebuilds thinking blocks), the re-serialization can't be coming from your layer — it has to be inside Claude Code's resume reconstruction. The --resume path rebuilds the latest assistant turn from the parsed transcript to continue it; that rebuild is the parse→struct→re-serialize step m13v described, and -p skips it because there's no prior turn to reconstruct. So your wrapper is exonerated, and the round-trip is internal to the resume path. That's consistent with why background-task resume and parallel-cancel repro most — both force a rebuild of the latest assistant turn.
Q2 — is --thinking disabled the only reliable workaround, or should DISABLE_INTERLEAVED_THINKING=1 avoid it? DISABLE_INTERLEAVED_THINKING=1 is not reliable for this class, and your route confirms it. cnighswonger's 2.1.148 disassembly (cited in the Cluster 13 tracker entry) shows that flag does not actually remove the signed-thinking surface, so the byte-drift hazard survives it. Two things that do work, with different costs:

  • --thinking disabled — removes the signed-block surface entirely, so it's reliable but degraded (you lose thinking).
  • Model downgrade--model claude-opus-4-7 (or claude-opus-4-6[1m]). The longitudinal signature data on #61133 / #62123 shows older generations carry zero "retry also failed" events for the same workflow, while it worsens 4.7 → 4.8. You're on claude-opus-4-8[1m], so dropping to 4.7 keeps signed thinking and lands on a generation with far fewer replay failures — usually the better trade than killing thinking outright.

Q3 — is the block structure enough, or would maintainers need more? I can't speak for the maintainers. For classification into this cluster, what you have (thinking + stop_reason=tool_use + missing tool_use + recovery prompt + timing) is already strong and matches the signature. The one piece that would let anyone confirm the re-serialize hypothesis definitively rather than infer it is a byte-level diff of the same thinking block keyed by id: emission-time bytes vs the bytes replayed on the resume turn. If your wrapper watches the transcript, capturing those two byte sequences for one failing block (even with the text content redacted — it's the byte/whitespace/key-order drift that matters, not the content) is the decisive artifact. That's the evidence that turns "the signature stopped validating" from a hypothesis into a demonstrated invariant violation.
Folding your route (PM2 → tmux → --resume interactive, wrapper-doesn't-touch-transcript) into the Cluster 13 evidence as the cleanest "the re-serialize is internal to resume, not the wrapper" data point. Thanks for the sanitized detail — the "claude -p clean / --resume fails" contrast is the part that pins it.