[BUG] Unrecoverable `400 … thinking/redacted_thinking blocks … cannot be modified` permanently bricks a session — reproduced across two independent sessions (v2.1.154)

Resolved 💬 7 comments Opened May 29, 2026 by michaelalexander-git Closed Jul 1, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

A Claude Code session entered a permanently unrecoverable state. Every message send fails with a 400 … thinking or redacted_thinking blocks in the latest assistant message cannot be modified error, and it never clears — every subsequent request in that conversation throws the same 400. There is no in-session recovery; the session is dead and any in-progress context is stranded.

This is not isolated to one corrupted transcript. The failure was reproduced in a second, freshly-started session that used no --resume and shared no transcript state with the first. The second session threw the same error class at a different coordinate (messages.1.content.35 vs. the first session's messages.7.content.4) — i.e., two independently-generated 400s, not one echoed error.

Both sessions were on v2.1.154 and both involved interleaved thinking (multiple signed thinking blocks within a single assistant turn) plus background sub-agents (Task/Agent tool, one continued mid-task / "resumed from transcript"). This points to a client-side bug in how Claude Code assembles/serializes signed thinking blocks into the outgoing API payload — the block sent no longer matches what the API originally signed, so the API rejects it.

Forensic detail supporting "send-time assembly, not on-disk state": the error's messages.N.content.M coordinates do not map to any on-disk transcript line where content[M] is actually a thinking block, meaning the request array sent to the API is assembled differently from the persisted transcript.

What Should Happen?

Signed thinking/redacted_thinking blocks should be preserved byte-for-byte through every request-assembly path — including background-sub-agent resume, context compaction, and long-idle resends — so they always match what the API signed.

Failing that, Claude Code should detect a thinking-block signature mismatch and self-heal (e.g., drop or regenerate the offending assistant turn) rather than looping on a fatal 400 that permanently bricks the session with no recovery path.

Error Messages/Logs

Session 1:

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


Session 2 (clean start, no resume — same error class, different coordinate):

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

Steps to Reproduce

I do not have a minimal deterministic repro, but both observed failures shared these conditions:

  1. Run a session that uses interleaved thinking (more than one signed thinking block per assistant turn).
  2. Have the session spawn a background sub-agent (Task/Agent tool), and/or continue/resume a background sub-agent mid-task ("resumed from transcript").
  3. Let the session sit idle for an extended period before the next send (the first failure followed a ~90-minute idle gap).
  4. Send the next message → it fails with 400 … thinking blocks … cannot be modified, and every send thereafter throws the same 400 permanently.

The fact that a second, completely fresh session (no resume, no shared state) reproduced the same error class on its own first substantial assistant turn suggests the trigger is in the request-assembly path rather than any single saved conversation.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.152

Claude Code Version

2.1.154

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

One earlier hypothesis ruled out (so triage doesn't chase it): On disk, every thinking block in both broken transcripts is stored with a valid signature but empty plaintext ("thinking":""). This looked like the corruption, but a control comparison against three healthy sessions shows they store thinking blocks identically — it's the normal on-disk convention (full text held in memory, only the signature persisted), not the cause:

| Session | State | Version | thinking blocks | empty-text | with-text |
|---|---|---|---|---|---|
| live, working | healthy | 2.1.154 | 9 | 9 | 0 |
| completed normally | healthy | 2.1.152 / 2.1.153 | 81 | 81 | 0 |
| completed normally | healthy | 2.1.145 / 2.1.152 | 109 | 109 | 0 |
| Session 1 | broken | 2.1.154 | 11 | 11 | 0 |
| Session 2 | broken | 2.1.154 | 12 | 12 | 0 |

Full .jsonl transcripts for both broken sessions exist and can be provided to Anthropic privately (they contain personal data, so they aren't attached to this public issue). I'm also submitting this via /bug from a separate healthy session, which carries the complete unredacted forensic detail and real session IDs.

A separate, unrelated model-reliability issue occurred in the same session (a background sub-agent produced fabricated tool results); it is being reported separately and is not the subject of this issue.

View original on GitHub ↗

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