2.1.154 regression: bridge/remote-control session replay re-sends persisted (empty-text, signed) thinking blocks -> 400 'thinking blocks cannot be modified', session unrecoverable
<!-- internal -->
Note: this description has been consolidated to reflect the full diagnosis. The earlier comments are the investigation trail; this body is the current, accurate summary.
Summary
On Claude Code 2.1.154, a session driven through the cloud bridge / remote control can return:
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.
The error is intermittent and self-recovering during live interactive use, but becomes a permanent, unrecoverable wedge when it fires during an unattended continuation (a scheduled wakeup, a backgrounded subagent returning, or a remote re-drive with no human reply). A wedged session returns the identical 400 on every subsequent turn — including new user messages — and only manual .jsonl surgery brings it back, after which it re-breaks on the next disk-reloaded turn.
Environment
- Claude Code 2.1.154, macOS,
entrypoint: cli - Session mirrored through the cloud bridge (transcript contains
bridge-sessionrecords) — i.e. remote-controlled from another device - Extended thinking enabled
Root cause
Thinking blocks are persisted to disk as {"type":"thinking","thinking":"","signature":"..."} — empty text, real signature — in every version. This is normal and is not the problem by itself.
The 400 fires when the latest assistant message is reconstructed from the stripped-on-disk transcript (empty text under a live signature) and re-sent. The API validates the signature against the now-empty content, the seal fails, and the request is rejected. A normal interactive turn instead rebuilds the request from the live in-memory message objects, which still hold the full thinking text, so the signature validates and the request passes.
What makes the difference between transient and terminal is whether a live, un-reloaded in-memory copy of the original thinking still exists in the process:
- Process continuously alive, never reloaded from disk → full thinking text still in memory → every turn (human or automated) rebuilds from it and succeeds. The 400 may flicker but always recovers.
- Process reloads from disk — a scheduled wakeup firing,
--resume, or a remote/bridge re-drive — → the good in-memory copy is overwritten by the stripped on-disk version → there is no longer an un-stripped copy anywhere → permanent. From this point no turn of any kind can fix it, including a fresh human message, because the recovery target no longer exists.
This is why a human message does not rescue an already-wedged session: by the time it arrives, the disk reload has already discarded the only good copy. It also explains why truncating the transcript and resuming only buys a single turn — --resume is itself a disk reload, so the session comes back living off stripped state; the first fresh turn generates new in-memory thinking and works, then the next disk-rebuilt turn re-breaks.
Version bisect
Count of bridge round-trips vs. count of thinking ... cannot be modified 400s, across sessions on one machine:
| Version | Bridge round-trips | thinking-400s |
|---|---|---|
| 2.1.131 | 0 | 0 |
| 2.1.150 | 122 | 0 |
| 2.1.150 | 87 | 0 |
| 2.1.152 | 57 | 0 |
| 2.1.154 | 17 | 3 (recovered — interactive) |
| 2.1.154 | 7 | wedged (unattended wakeup) |
Heavy bridge use on 2.1.150–2.1.152 (57–122 round-trips) never reproduces it. Both 2.1.154 bridge sessions do. This isolates the defect to a replay/reconstruction change in 2.1.154: earlier builds stripped or re-hydrated the latest-message thinking block before replay; 2.1.154 sends the empty-text + signature block verbatim.
Reproduction
- On 2.1.154, run a thinking-enabled session and enable the cloud bridge / remote control.
- Trigger an unattended continuation while the latest assistant turn contains a thinking block — e.g. a
ScheduleWakeup/scheduled wakeup, a backgrounded subagent returning, or a remote re-drive with no human reply. - The continuation rebuilds the latest assistant message from the stripped-on-disk transcript and the API returns the 400. With no live human turn in an un-reloaded process to fall back on, every subsequent turn repeats it.
Expected
The replay/reconstruction path should strip or re-hydrate persisted thinking blocks (as builds ≤ 2.1.152 did) so that re-sending a disk-loaded assistant message cannot fail signature validation. At minimum, a session should not be permanently unrecoverable from this state.
Recovery / workaround
- Recovery: manual
.jsonlsurgery — truncate to the last record whoseparentUuidchain has no danglingtool_useand no trailing thinking block, repoint the leaf, resume. Re-breaks on the next disk-reloaded turn. - Workaround: downgrade to 2.1.152, or do not leave a thinking-enabled 2.1.154 session running unattended over the bridge (drive it live; avoid scheduled wakeups and walk-away background work).
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗