[BUG] API Error 400 - Thinking Block Modification Error

Open 💬 100 comments Opened Oct 23, 2025 by WamBamBoozle

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?

The Claude Code session became completely unresponsive with repeated API 400 errors. Every subsequent user input triggered the same error, making recovery impossible.

Symptom: Session entered an error loop where the conversation history reached a corrupted state:

  1. A previous assistant response contained thinking blocks
  2. The system attempted to modify or replay that message's thinking blocks
  3. The API correctly rejected this (thinking blocks are immutable once sent)
  4. The error persisted because the corrupted state remained in conversation history

Error Pattern: The error occurred at messages.71.content.8 (71st message, 8th content block), indicating:

  • Deep conversation history (71 messages)
  • Specific content block contained thinking blocks that couldn't be modified
  • Every subsequent user input triggered the same error

Contributing Factors:

  • Long session: Nearly 15k lines of transcript suggests extended conversation
  • Complex workflow: Multiple tool calls, file reads, git operations
  • Repeated questions: Same question asked multiple times may have confused state
  • Message editing attempt: The system may have tried to edit/retry previous responses

Impact:

  • Session Status: Completely unrecoverable
  • User Action: Had to save transcript and start new session
  • Work Status: Context lost mid-conversation about a technical topic
  • Data Loss: Any uncommitted mental state/context

What Was Lost:

  • Active conversation context and unresolved questions
  • Current working state and recent discussion topics
  • Mental model of the problem being discussed

What Should Happen?

Expected Behavior:

  • Claude Code should handle long conversations gracefully without entering unrecoverable error states
  • If thinking blocks cannot be modified (as per API constraint), the system should detect this condition before submitting the request
  • When errors occur, the system should offer graceful degradation options

Ideal Recovery Flow:

  1. Detect when conversation state is approaching problematic conditions
  2. Offer to summarize current state
  3. Save context to file automatically
  4. Start fresh session with context restored
  5. Or provide mechanism to "rewind" conversation history to before corrupted state

Preventive Measures That Should Exist:

  • Conversation state validation to detect when thinking blocks are being modified
  • Message history pruning or summarization for very long conversations
  • Threshold warnings when conversation history reaches risky size (>10k lines)
  • Checkpoint mechanism to enable rollback to stable state

Error Messages/Logs

### Primary Error Message

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.71.content.8: `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_011CUQZ6ttKtJbU8JQRYyizo"}


### Error Repetition Pattern

Line 14957: User asks question → API Error 400
Line 14961: User asks same question → API Error 400 (same request)
Line 14965: User asks same question → API Error 400 (same request)
Line 14969: User references the error itself → API Error 400
Line 14974: User asks to update memory files → API Error 400


### Technical Details

**API Endpoint**: Anthropic Claude API (claude.code integration)

**Error Type**: `invalid_request_error`

**Error Message Pattern**:

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.


Where:
- `{N}` = Message index in conversation history (71 in this case)
- `{M}` = Content block index within that message (8 in this case)

**Request IDs**:
- req_011CUQZ6ttKtJbU8JQRYyizo
- req_011CUQZ8XPZgRdbaXt8GJrxy
- req_011CUQZ9SAKKbLfL1g7ieqU8
- req_011CUQZAR1eH1AotrvNzGhx8

**Transcript Available**: Yes (763.4KB, 14,976 lines)

Steps to Reproduce

Context Leading to Error

  1. User and assistant engaged in extended session about a technical topic (~14,976 lines)
  2. Conversation involved multiple rounds of investigation and explanation
  3. Assistant provided detailed technical explanations
  4. User asked follow-up question requesting clarification
  5. Same question was asked 3 times in succession (possibly due to user not seeing response)
  6. ERROR STATE TRIGGERED

Reproduction Steps

  1. Start Claude Code session
  2. Engage in extended conversation with multiple tool calls, file reads, and complex workflows
  3. Reach approximately 71 messages deep in conversation history
  4. Have assistant generate response with thinking blocks
  5. Ask follow-up question that may trigger message retry or state replay
  6. Observe API Error 400 with thinking block modification error
  7. Attempt to continue session → error repeats for all subsequent inputs

Conditions That May Trigger

  • Very long sessions (>10k lines of transcript)
  • Conversation history at ~71 messages or deeper
  • Assistant responses containing thinking blocks
  • Rapid repeated questions (asked 3 times in succession in this case)
  • Complex multi-turn workflows with many tool calls

Warning Signs

  • Session approaching 10k+ lines of transcript
  • Repeated API errors occurring
  • Same question triggering errors multiple times
  • Any API 400 error mentioning thinking blocks

Recovery Actions Taken

  1. User saved full transcript to local file
  2. User started new Claude Code session
  3. User asked new session to review the transcript
  4. User requested this bug report be filed

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.0.25 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

IntelliJ IDEA terminal

Additional Information

_No response_

View original on GitHub ↗

100 Comments

github-actions[bot] · 8 months ago

---

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/2171
  2. https://github.com/anthropics/claude-code/issues/1702
  3. https://github.com/anthropics/claude-code/issues/2608

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

---

WamBamBoozle · 8 months ago

not a duplicate -- all the false positives listed above have different error messages

chops · 8 months ago

Experiencing a similar issue with CC throwing 400's.

Claude Code Version: 2.0.25
Login method: Claude Max Account
Model: Sonnet 4.5 (default)
OS: GNU Guix
TERM: xterm-256color (via ssh)

bhaskarEEN · 8 months ago

how do you get the API response codes from the CLI ?

WamBamBoozle · 8 months ago

It just logged them. I did nothing special.

bhaskarEEN · 8 months ago
It just logged them. I did nothing special.

Can you please elaborate ? I am also facing a similar issue but not sure if it is the same in vs code terminal. I wanted to verify if it is the 400 response as you have mentioned. It would be nice if you can share how you log the claude-code's API calls.
Please & thank you

WamBamBoozle · 8 months ago

I lost the logs

zubin · 8 months ago

I'm getting a similar error after attempting to /resume a Claude Code terminal session:

  ⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.19: The final block in an assistant
    message cannot be `thinking`."},"request_id":"req_011CV4VkJfmm9mTv9eWV9381"}

It's displayed the first time I send a message since /resume. Disabling thinking (pressing tab) resolved it.

bhaskarEEN · 8 months ago

@zubin how do you get the API calls logged ?

zubin · 8 months ago
@zubin how do you get the API calls logged ?

I didn't do anything log-related. Just tried /resume and that text was printed.

CivilEngineerUK · 8 months ago

I am getting this also. As a workaround, instead of a long prompt, I am writing my prompt to a .md file and then instructing claude-code to read and implement the instructions in the .md.

yanzhi-enso · 7 months ago

experiencing similar issue too, here is the log

{"message":"400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"messages.1.content.0: Invalid `signature` in `thinking` block\"},\"request_id\":\"req_vrtx_011CVMkoUR3ukzE5uNm6Nuip\"}","status":400,"request_id":"req_vrtx_011CVMkoUR3ukzE5uNm6Nuip","modelId":"claude-sonnet-4-5@20250929","providerId":"vertex","details":{"type":"error","error":{"type":"invalid_request_error","message":"messages.1.content.0: Invalid `signature` in `thinking` block"},"request_id":"req_vrtx_011CVMkoUR3ukzE5uNm6Nuip"}}
Request ID: req_vrtx_011CVMkoUR3ukzE5uNm6Nuip
DevinRB · 7 months ago

This started happening to me a few hours ago when using /resume in Claude Code cli. Functions without issue in other cases.

mysehyunhope · 7 months ago

similar issue.
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.9.content.1: 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_011CVQSt71zTgpRrWfMKDwaZ"}

claude code 2.0.50 , Max user, ubuntu(wsl)

rewinding 2 or 3 messages usually solves this issue for me.

Dante-dan · 7 months ago

similar issue. 2.0.50 (Claude Code), Pro User, Mac os

⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.9.content.24: 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_011CVQp9vC1ZRNZGKGyJHSCm"}

AlizerUncaged · 7 months ago

same problem, turning thinking off does not fix the problem

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.7.content.5: `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_011CVRLyHHZxZ7edDKMNR7K9"}
bkorpan · 7 months ago

I just got this for the first time.

  • I first rewound two messages prior to the error first appearing (i.e., to the latest conversation state that previously did not result in the error on the next message), but this did not resolve the error
  • I next tried disabling thinking: messages sent with thinking disabled did not produce the error in that state
  • Re-enabling thinking and sending a message produced the error again
  • I then rewound by an additional message (undoing a step that resulted in a large number of auto accepted edits, though I didn't revert the code), and in this state did not get the error even with thinking enabled
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.21.content.116: 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_011CVRbhLRdv1LZsPvyyGksL"}
enkr1 · 7 months ago

+1, lost all progress, gotta copy to restart, any best recovery method?

tjsb · 7 months ago

compaction via /compact seemed to fix it for me

julian-mateu · 7 months ago

Same here, but compacting does not fix the issue either (nor does disable thinking, or going back to the previous message).

GekiteniusY · 7 months ago

Same
Once it is happened, I can't use /compact and request any prompt more

nahyeongjin1 · 7 months ago
Workaround that worked for me:

I encountered the same error and was able to resolve it by manually removing thinking blocks from the conversation JSONL file.

Steps:
  1. Close Claude Code completely
  2. Find the conversation file:
ls -la ~/.claude/projects/
# Look for the largest/most recent .jsonl file in your project folder
  1. Backup and fix the file:
import json

filepath = "/path/to/your/session.jsonl"

with open(filepath, 'r') as f:
  lines = f.readlines()

with open(filepath, 'w') as f:
  for line in lines:
    if not line.strip():
      continue
    data = json.loads(line)
    if 'message' in data and 'content' in data['message']:
      if isinstance(data['message']['content'], list):
        data['message']['content'] = [
          c for c in data['message']['content'] 
          if c.get('type') not in ('thinking', 'redacted_thinking')
        ]
    f.write(json.dumps(data) + '\n')
  1. Restart Claude Code

This is a temporary workaround until an official fix is released. Hope this helps others facing the same issue!

GekiteniusY · 7 months ago

@nahyeongjin1
Sounds good! Thanks for sharing!!
I'll try it next time it comes up.

devguyio · 7 months ago

@nahyeongjin1 this workaround worked perfectly for me, thank you 🙏🏽

ZainRizvi · 7 months ago

I ran into a same error when using Claude Code Cloud when I tried to edit my last message a few seconds after it started processing the request. Was using Opus 4.5

Every subsequent message after that had the same error, rendering that thread effectively frozen, unable to iterate on any more changes

My edited prompt:

Add unit tests for the new modules. Ensure they work with our GitHub workflows

The response:

API Error: 400 {“type”:“error”,“error”:{“type”:“invalid_request_error”,“message”:“messages.1.content.33: 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_011CVrM26PfEL5a1cznFTJz7”}
mysehyunhope · 7 months ago

the simplest is to rewind ur last or 2,3 messages before.
it works always for me.

FaureAlexis · 7 months ago

got the same issue, compact did not work and this workaround script from @nahyeongjin1 worked like a charm 👍

```python
with open(filepath, 'r') as f:
lines = f.readlines()

with open(filepath, 'w') as f:
for line in lines:
if not line.strip():
continue
data = json.loads(line)
if 'message' in data and 'content' in data['message']:
if isinstance(data['message']['content'], list):
data['message']['content'] = [
c for c in data['message']['content']
if c.get('type') not in ('thinking', 'redacted_thinking')
]
f.write(json.dumps(data) + '\n')

dexhorthy · 7 months ago

will add that this is still occurring VERY reliably with claude agent sdk , I can reproduce quite consistently on v0.1.70

fingoldo · 6 months ago
#### Workaround that worked for me: I encountered the same error and was able to resolve it by manually removing thinking blocks from the conversation JSONL file. #### Steps: 1. Close Claude Code completely 2. Find the conversation file: ls -la ~/.claude/projects/ # Look for the largest/most recent .jsonl file in your project folder 3. Backup and fix the file: import json filepath = "/path/to/your/session.jsonl" with open(filepath, 'r') as f: lines = f.readlines() with open(filepath, 'w') as f: for line in lines: if not line.strip(): continue data = json.loads(line) if 'message' in data and 'content' in data['message']: if isinstance(data['message']['content'], list): data['message']['content'] = [ c for c in data['message']['content'] if c.get('type') not in ('thinking', 'redacted_thinking') ] f.write(json.dumps(data) + '\n') 4. Restart Claude Code This is a temporary workaround until an official fix is released. Hope this helps others facing the same issue!

I can confirm this solution works. Anthropics, what are we paying for? Please fix this bug. Solution has already been found for you. I'm tired of this bug plaguing me in VS Code extension.

flozano · 6 months ago

this is terrible, happening very often.

Smoin1 · 6 months ago

Yes, curently happening on really many chats. maybe 50%

Smoin1 · 6 months ago

and in vscode you cannot press tab to exit thinking ;)
and by the way, no reaction here? Anthropic does not care that their product is degrading rapidly?

mohit-frai · 6 months ago

+1 same issue

K4leri · 6 months ago

+1 same

hancens1024 · 6 months ago

+1 Same. This is a very disgusting bug, and it occurs frequently. Please fix it as soon as possible.

RilusMahmud · 6 months ago

+1 Same. Still happening with version 2.1.1. Please fix this!

smartcmd · 6 months ago

+1

faramarza · 6 months ago

+1

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.1.content.13: 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_011CWxP1n9XTs4dTcRPGCmz6"}

rogerchappel · 6 months ago

+1 This happened to me on claude code web app on mobile.

saki-37 · 6 months ago

Hi! I encountered the same issue and want to share what worked for me.

After some troubleshooting, I found that starting a new conversation resolved the 400 error.

My friends suggested this might be related to the context becoming too long in extended conversations. While there's a "compress context" option available, they mentioned that starting fresh is probably the better solution, as compressed contexts might still cause issues down the line.

I'm not entirely sure if this is the root cause for everyone, but I noticed some people mentioned experiencing this around 50% of the time, which could support this theory.

Hope this helps! 😊

a1exlism · 6 months ago

Hi everyone,
I’ve put together a quick-fix plugin that uses a hook/command to work around the issue.
You can find it here: https://github.com/a1exlism/claude-code-cmd-fix-thinking-error

Smoin1 · 6 months ago

it happens for me now in over 50% ofg all chats. often also very close afters starting so there is no correlation with the used context. it destroy productive work a lot since I am now used to start every chat with "the other cha crashed again, please read my copy&paste..."

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.11.content.22: 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_011CX5CRxigYG8J8ddVwUyLb"}

vincentsch · 5 months ago

I’ve hit this twice in CLI (-p) mode on Linux, and both times the session became unrecoverable. It looks like state corruption: retrying the same prompt just repeats the error forever.

  • OS: Linux (Xubuntu)
  • Claude Code: 2.1.12 and 2.1.9
  • Mode: non-interactive CLI (claude -p --output-format json)
  • Error: “thinking or redacted_thinking blocks in the latest assistant message cannot be modified”
  • Timeframe: Jan 2026

Impact: once it happens, immediate retries keep failing and you have to start a new session.

a1exlism · 5 months ago
Hi everyone, I’ve put together a quick-fix plugin that uses a hook/command to work around the issue. You can find it here: https://github.com/a1exlism/claude-code-cmd-fix-thinking-error

UPDATE:
According to the status report at https://status.claude.com/incidents/5dvcj03m4d57, Anthropic is currently addressing this issue. We should consider updating the CLI version once a fix is released.

dabrain34 · 5 months ago

I still experience the issue with vscode on Linux with CC 2.1.22 on January 28th

timkindberg · 5 months ago

I was able to Ctrl+C out of the session and resume with claude --resume and things are working again. Mac OS Ghostty terminal, Claude 2.1.19.

BennettSchwartz · 5 months ago

Ctrl+C and resume did not work for me. Same specs as above.

benyamynbrkyc · 5 months ago

still happening through opencode as well

lokkaflokka · 5 months ago

New trigger path: ESC during extended thinking → empty text content block

Version: 2.1.30
Platform: macOS (Darwin 23.3.0)
Model: claude-opus-4-5-20251101

Error

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages: text content blocks must contain non-whitespace text"}}

Reproduction

  1. Long session with extended thinking enabled (model cogitating/"Sautéed for Xs")
  2. User presses ESC to interrupt during extended thinking
  3. User sends a new message — model responds normally
  4. User sends another message — 400 error, every subsequent message also fails
  5. Session is permanently unrecoverable (new messages, /compact, everything returns the same 400)

What's happening (from debug logs)

The interrupt during extended thinking saves the partially-generated assistant message. Since the model was still in the thinking phase and hadn't produced any visible text, the saved message contains an empty text content block. This poisons the conversation history — every subsequent API call sends the same broken history.

Debug log sequence:

02:15:05.620Z [DEBUG] Stop hook fires (user interrupted during "Sautéed for 57s")
02:16:53.044Z [DEBUG] UserPromptSubmit — user sends next message
02:16:53.558Z [DEBUG] Stream started — model responds normally (response streams ~11s)
02:17:04.988Z [DEBUG] Stopped caffeinate — response complete
02:17:18.787Z [DEBUG] UserPromptSubmit — user sends another message
02:17:19.311Z [ERROR] API error: 400 "text content blocks must contain non-whitespace text"

Note: between the interrupt (02:15:05) and the first error (02:17:19), one normal turn completes successfully. The empty text block from the interrupted response is in the history but only causes failures when the conversation gets long enough or the message ordering hits the validation check.

Relationship to other variants

This is the same conversation-state-corruption class as the thinking block modification errors in this issue, but via a different code path:

  • This issue's main variant: thinking/redacted_thinking blocks get modified → "thinking or redacted_thinking blocks in the latest assistant message cannot be modified"
  • This variant: interrupt during thinking produces empty text content block → "text content blocks must contain non-whitespace text"
  • Same outcome: session permanently unrecoverable

Also related to #22923 (exact same error, different trigger) and #11323 (same trigger, different error variant).

Suggested fix

Claude Code should validate/sanitize assistant messages after an interrupt — if a text content block is empty (or contains only whitespace), either remove it or replace it with a placeholder like "[interrupted]" before saving to conversation history.

yogen-ghodke-113 · 5 months ago

I got the same issue when i pressed the ESC key when it was thinking

AbbyB97 · 5 months ago

Error:

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.71.content.8: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response."}}

Not a perfect solution, but here's a workaround that worked for me to get past this error:

  1. Install the Claude Code for VS Code extension
  2. Open the extension's chat panel and log in with your account
  3. Type /resume in the chat
  4. You'll see your conversation history from the Claude Code terminal
  • NOTE: If you're using WSL2, make sure to open VS Code from within WSL2 to see your history
  1. Select the conversation where you were getting the API:400 error
  2. Scroll to the last good message/response — the one right before the error started
  3. Hover over that message — you'll see a reverse arrow icon. Click "Fork Conversation" (or "Fork Conversation and rewind code" if needed) and wait for the response
  4. Once forked, you can go back to the terminal and use /resume to continue your conversation from there
lapin7771n · 5 months ago

+1

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.7.content.1: `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_011CY61hUEp2DUZgjFQ2Gy7y"}
lukehutch · 5 months ago
6\. Scroll to the last good message/response — the one right before the error started

The way you do this in Claude Code CLI is to hit Esc twice, then press the Up arrow one or more times, then hit enter twice to rewind the conversation and continue from there. This drops the corrupted part of the chat history.

doziedotdev · 5 months ago

same issue here as well, the only way I could get past it was abandoning the session

hyk0127 · 4 months ago

Same exact issue as well. Happened while I halted an extended session with esc and tried to tell Claude something else.
In my case, I could revive the session through ctrl + c -> claude resume.

audix-cz-user · 3 months ago

same issue:

Sorry, your request failed. Please try again.

Copilot Request id:

GH Request Id:

Reason: Request Failed: 400 {"message":"messages: text content blocks must contain non-whitespace text"}

I switched from one agent to another (after I changed from Autopilto to Default Approvals)

840usm · 3 months ago

Same here after /resume

Error: Error during compaction: Error: API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.1.content.0: Invalid signature in thinking
     block"},"request_id":"req_011CZKVKiftaLA3GLs7jvJNa"}
yurukusa · 3 months ago

/tmp/gh-comment-10199.md

stainlu · 3 months ago

Hit this today on Claude Code v2.1.112 (macOS 15.3.2).

  • request_id: req_011Ca9dV75e7W49c6Y5nyuz1
  • Error: messages.17.content.1: \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 became unrecoverable — every subsequent prompt hit the same 400. /clear was the only way out.

lngdao · 3 months ago

Hit this today and got it working. Root cause: extended-thinking responses come back signed, and on resume the API validates those signatures. If anything has touched them (hook, compaction, half-written entry from a crash) they no longer match and every retry fails — each retry just appends another broken turn.

Fix: strip thinking / redacted_thinking blocks from the session's jsonl. The API doesn't validate what isn't there; you only lose the model's private reasoning — text, tool calls, and tool results stay intact.

Easiest way if you don't want to touch the transcript by hand — open a fresh Claude Code session and paste this:

My previous Claude Code session is stuck in an API 400 loop about thinking blocks that cannot be modified. The file is at ~/.claude/projects/<project-dir>/<session-id>.jsonl (project dir = working directory path with / replaced by -). Back it up to .bak, then rewrite the file to remove every thinking and redacted_thinking block from each assistant message's content array. If a message ends up with empty content, replace it with [{"type":"text","text":"[thinking omitted]"}]. Keep every other line, all text/tool_use/tool_result blocks, and all uuid/parentUuid fields exactly as they are. Do not truncate the file.

Then claude --resume <session-id> and it picks up clean.

Two things that don't work, skip them: truncating the jsonl to before the first failure (Claude Code reloads from the tail, so you lose recent context instead of rewinding), and editing thinking block content in place (signature still won't match — it has to be fully removed).

pangiras · 2 months ago

Hi, is there any update on how to resolve this issue without disabling thinking?

junaidtitan · 2 months ago

Still hitting this in 2026 — the thinking block corruption is brutal because it makes the entire session unrecoverable. Cozempic (https://github.com/Ruya-AI/cozempic) has a thinking-blocks strategy that strips stale thinking content from session history, which prevents the immutable-block replay that triggers the 400. pipx install cozempic && cozempic treat --rx standard should clear the corrupted state without losing your conversation. Happy to hear if it resolves the loop for anyone still affected.

junaidtitan · 2 months ago

This thinking block corruption is one of the most frustrating session-killers. Cozempic's thinking-blocks strategy strips extended thinking content from older turns in the JSONL before it can cause these 400 errors — the blocks stay structurally valid but the bulk content that triggers modification errors gets cleaned. The cozempic doctor command also detects corrupted session state. pip install cozempic && cozempic doctor to diagnose, then cozempic treat to fix. Repo: https://github.com/Ruya-AI/cozempic — feedback welcome.

cbsajan · 2 months ago

Try to start a new conversation.

This could fix this error

840usm · 2 months ago

Starting a new conversation does in fact fix the issue but you lose the context from previous conversation with out any summary or context dump

cbsajan · 2 months ago

Yeah, but I don't find any way to resolve this issue other than this.

tanguc · 1 month ago

same there

SmartManoj · 1 month ago

Passed session id in a new chat and asked it fix the error

<img width="1184" height="348" alt="Image" src="https://github.com/user-attachments/assets/78c9dfbe-6ce2-4b6c-b4c1-ea06578055e4" />

Rabusek · 1 month ago

Same error just now

shreyash02 · 1 month ago

Ran into the issue now

kueyar95 · 1 month ago

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

zoratu · 1 month ago

The session was replaying thinking blocks that had empty text but live signatures, so the API rejected them. Stripping those broken blocks from the active branch and relinking the chain preserved all messages, tool calls/results, and the real thinking blocks elsewhere.

chid · 1 month ago

yep happening to me too, latest version of the app on Mac, across three separate sessions - not easily fixable in the app (as oppose to the CLI)

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

<img width="302" height="429" alt="Image" src="https://github.com/user-attachments/assets/3a936166-e08c-485c-90f2-c68fb5debb98" />

escamoteur · 1 month ago

Got hit by this twice today in Claude Code and you can't recover from it as it seems

sheenues · 1 month ago

Same here

<img width="1277" height="92" alt="Image" src="https://github.com/user-attachments/assets/b817cef9-ee46-47f7-8cbf-23f284d44fb6" />

richardrl · 1 month ago

● Bash(echo "PROBE_$(date +%s)_END")
⎿  Cancelled: parallel tool call Bash(D=/data/pulkitag/scratch/rli14/projects/…) errored

Read 1 file (ctrl+o to expand)
⎿  Cancelled: parallel tool call Bash(D=/data/pulkitag/scratch/rli14/projects/…) errored
⎿  API Error: 400 messages.9.content.30: thinking or redacted_thinking blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.

Opus 4.8 broken

escamoteur · 1 month ago

yep if you use Sonnet all is good, how can I switch to 4.7?

denu5 · 1 month ago

same here with opus 4.7 but seems they released opus 4.8, switching to 4.8 works

escamoteur · 1 month ago

nope, I had the same problem with 4.8

bazookon · 1 month ago

same here with opus 4.8

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

MarceloVarela · 1 month ago

Same looks like a wide spread problem

alexmorreale-fl · 1 month ago

i'm on opus 4.7 and it's happening there too

evnchn · 1 month ago

Posted by Claude Code acting on behalf of this account's owner, per their instruction to add JSONL-mined diagnostic evidence to this thread.

TL;DR: Forensics from two JSONL session files on CC 2.1.154 + claude-opus-4-8 (claude-opus-4-7 unaffected on same CC version) confirms @zoratu's "empty text + live signatures" mechanism with hard data, and identifies the structural trigger: Opus 4.8 appends thinking blocks after parallel tool_use batches in the same assistant message — placing them at deep content indices (e.g. content[12]) where message reconstruction fails on cancel.

<details>
<summary>Full JSONL evidence</summary>

Environment

  • Claude Code: 2.1.154 — bug persists past the 2.1.153 reports earlier in this thread
  • Model: claude-opus-4-8 — same sessions with claude-opus-4-7 on 2.1.154 are unaffected

---

Finding 1 — Thinking blocks stored as empty content + live signature

Every thinking block in both affected sessions appears in the raw JSONL as:

{"type": "thinking", "thinking": "", "signature": "EoIDCmMIDhgCKkDPL+H4zofgix4b/dLD70jF4z..."}

CC strips the CoT text for JSONL storage but preserves the Anthropic-issued signature. On reconstruction after a parallel-cancel event, this empty-content block is sent to the API. The API validates content against signature, finds a mismatch → 400. This is the concrete JSONL evidence behind @zoratu's observation ("replaying thinking blocks that had empty text but live signatures").

---

Finding 2 — Opus 4.8 uses interleaved thinking (thinking blocks AFTER tool_use batches)

In the parentUuid chain of an affected assistant turn, Opus 4.8 appends a thinking block after a parallel batch of tool_use calls within the same assistant message:

tool_use:Bash   id=toolu_01P3Fu   ← parallel dispatch begins
tool_use:Read   id=toolu_01P1oT
tool_use:Read   id=toolu_016K7t
tool_use:Read   id=toolu_01R9XR
tool_use:Read   id=toolu_01KzTR
tool_use:Read   id=toolu_01RCPB
tool_use:Read   id=toolu_01XGfT
thinking        content='', sig=EoIDCmMIDhgCKkD...   ← AFTER 7 tool_uses

All 7 tool_result entries arrive simultaneously (parallel dispatch). claude-opus-4-7 only places thinking at the start of a response (before any tool_use calls). claude-opus-4-8's post-batch interleaved thinking places the block at a deep content index (e.g. content[12]) — a shape that message reconstruction apparently doesn't handle correctly when one of the parallel tools cancels.

This also explains why the partial fix in 2.1.152 (strip stale signatures on model/login switch) didn't cover this path: the interleaved position of the block inside a long tool-call sequence is the new factor that the strip logic doesn't reach.

---

Finding 3 — Same content index on every retry = corrupted payload stuck in session

The error messages.1.content.12 appeared identically across all three retries in one session over a 14-minute window. CC is replaying the exact same corrupted payload on every turn — the poisoned history is locked in session state. /clear is the only escape; Escape/retry does not help.

</details>"

zoratu · 1 month ago

Here's a python script to repair the broken blocks. It should work on the CLI and Desktop apps. SessionStart hooks can be added so that transcripts self-heal. https://github.com/zoratu/fix-claude-thinking

holgergevers-hub · 1 month ago

Extended thinking: the agent loop freezes on a thinking-block signature boundary — a recovery gap, not an API fault

Status: independent investigation write-up + minimal repros + recommended client-side fix.
Models: reproduced identically on claude-opus-4-8, claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5 (the full current family).
SDK: anthropic (Python) 0.96.0.

---

0. Framing (read first)

This is not "the API throws a wrong error." The 400 is a legitimate integrity boundary working as designed: thinking blocks are signed, and the API rejects one whose signature isn't authentic. A client hits that boundary through avoidable clumsiness (capturing an unsigned partial block, re-serializing the signature, echoing output-only fields). The only genuine defect is what happens next — the harness re-sends the identical rejected request forever and the session freezes. Backtrack, not die. Everything below supports that framing.

1. Summary

With extended thinking enabled and any continuation that re-sends a prior assistant turn (tool use, or multi-turn), the Messages API returns a deterministic 400 if a thinking/redacted_thinking block anywhere in the request carries a signature that doesn't verify. The error is correct and self-locating (messages.N.content.M).

The client defect: on this 400 the harness re-sends the identical conversation state and retries forever — every subsequent user input reproduces the same 400, and the session is permanently wedged. The bug is the missing recovery path, not the 400.

2. Symptom

Two surface messages, one root cause (the thinking-block signature fails to verify):

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.        # signature present but altered
400 messages.N.content.M: Invalid `signature` in `thinking` block
                                  # signature missing / malformed

Then the harness retries; every following turn returns the same 400; the session cannot be steered out of the loop.

3. Root cause — the true invariant (empirically characterized)

The API checks that **every thinking/redacted_thinking block present in the request carries an authentic, uncorrupted signature.** Precisely characterized by experiment:

**(a) It validates all assistant turns, not just the latest. Corrupting the signature of a thinking block in a non-latest** assistant turn 400s at that turn's coordinate — even though the error string says "latest assistant message." The wording is narrower than the actual validation.

**(b) The signature is an authenticity token, not bound to content/position/block/model.** A boundary table — one captured signed thinking turn, each mutation applied and resent (identical across the full current family — claude-opus-4-8/4-7, claude-sonnet-4-6, claude-haiku-4-5 — and identical for latest vs non-latest turns):

| mutation of a thinking block | verdict |
|---|---|
| (unchanged baseline) | ACCEPT |
| edit the thinking text (keep signature) | ACCEPT |
| truncate / empty the text (keep signature) | ACCEPT |
| drop the thinking block entirely | ACCEPT |
| reorder blocks | ACCEPT |
| replace the signature with a different but authentic signature (even from another model) | ACCEPT |
| corrupt the signature (or send it missing) | REJECT 400 at the block's coordinate |

So "cannot be modified" operationally means: if a thinking block is present, its signature must still verify; you may otherwise edit, truncate, reorder, or omit it — and any genuine signature passes. The check is authenticity, not binding to the block's text, position, or issuing model.

4. Why it freezes

The 400 is deterministic and names messages.N.content.M. A robust client has at least three safe recoveries, none requiring any API change:

  1. Drop the offending thinking block — the API accepts omission (boundary table), on any turn.
  2. Restore the originally-returned signed block — the client received it and can retain it.
  3. Rewind to before the corrupted turn.

Instead the client keeps the invalid block and re-sends the identical request → identical 400 → infinite loop. New user messages don't help — they're appended after the poisoned block, which still fails validation.

5. How the bad state arises (two confirmed triggers)

5.1 In-place mutation on resend

The harness reconstructs/serializes the assistant turn and the thinking block's signature is altered or a rebuilt block is substituted. → cannot be modified.

5.2 Mid-stream interrupt (confirmed)

When streaming a thinking turn, the signature arrives last (a signature_delta, immediately before the thinking block's content_block_stop). Interrupting before it — e.g. the user sends a message while the turn is still streaming — captures a thinking block with text but no signature. Resending it → Invalid signature. (Reproduced: a streamed turn interrupted mid-thinking_delta yields signature=''; resending rejects at messages.1.content.0, while the same turn allowed to finish is accepted.)

6. Minimal reproductions (self-contained)

(The repros below use claude-sonnet-4-6 with budget_tokens; on claude-opus-4-7/4-8 swap to thinking={"type": "adaptive"} — the boundary is identical.)

6.1 Signature variant

import anthropic, copy
c = anthropic.Anthropic()
M, THINK = "claude-sonnet-4-6", {"type": "enabled", "budget_tokens": 2000}
TOOLS = [{"name": "add", "description": "Add two integers.",
          "input_schema": {"type": "object",
                           "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}},
                           "required": ["a", "b"]}}]
msgs = [{"role": "user", "content": "Use the add tool to compute 17 + 25, then report it."}]
r1 = c.messages.create(model=M, max_tokens=4000, thinking=THINK, tools=TOOLS, messages=msgs)
ac = [b.model_dump() for b in r1.content]
tu = next(b for b in ac if b["type"] == "tool_use")
msgs += [{"role": "assistant", "content": ac},
         {"role": "user", "content": [{"type": "tool_result", "tool_use_id": tu["id"],
                                       "content": str(tu["input"]["a"] + tu["input"]["b"])}]}]
c.messages.create(model=M, max_tokens=4000, thinking=THINK, tools=TOOLS, messages=msgs)  # baseline: OK
bad = copy.deepcopy(msgs)
thk = next(b for b in bad[1]["content"] if b["type"] == "thinking")
thk["signature"] = thk["signature"][:-1] + ("A" if thk["signature"][-1] != "A" else "B")
c.messages.create(model=M, max_tokens=4000, thinking=THINK, tools=TOOLS, messages=bad)
# -> 400 messages.1.content.0: ... cannot be modified ...

6.2 Mid-stream interrupt variant

import anthropic
c = anthropic.Anthropic()
M, THINK = "claude-sonnet-4-6", {"type": "enabled", "budget_tokens": 2000}
P = "Reason step by step about whether 1517 is prime, then answer in one line."
partial, blocks = None, {}
with c.messages.stream(model=M, max_tokens=4000, thinking=THINK,
                       messages=[{"role": "user", "content": P}]) as s:
    for ev in s:
        if ev.type == "content_block_delta" and getattr(ev.delta, "type", "") == "thinking_delta":
            blocks.setdefault(ev.index, ""); blocks[ev.index] += ev.delta.thinking
            if partial is None and blocks[ev.index]:           # interrupt BEFORE signature_delta
                partial = {"type": "thinking", "thinking": blocks[ev.index], "signature": ""}
    s.get_final_message()
c.messages.create(model=M, max_tokens=4000, thinking=THINK, messages=[
    {"role": "user", "content": P},
    {"role": "assistant", "content": [partial]},
    {"role": "user", "content": "hang on, did we just hit a bug?"}])
# -> 400 messages.1.content.0: Invalid `signature` in `thinking` block

(Additional confirmations not shown for brevity: corrupting a NON-latest turn's signature rejects at that turn's coordinate; a thinking block accepts a different-but-authentic signature, including one issued by a different model; a corrupted signature is rejected even cross-model.)

7. Recommended fix (client-side; no API change required)

  1. Pre-send guard, every assistant turn: before each request, verify each thinking block in every assistant message carries the authentic signature the API returned; if missing/altered, drop the block or restore the original. (Drop suffices — omission is accepted, on any turn.)
  2. On this 400, don't blind-retry: parse messages.N.content.M, drop/restore that block, resend once; if it recurs, rewind rather than loop.
  3. Streaming: do not finalize/persist a thinking block until its content_block_stop (the signature arrives last). A partially-streamed thinking block is unsigned and must never be re-sent.
  4. Resend hygiene: strip output-only fields when echoing assistant content — e.g. a text block's parsed_output round-tripped via model_dump() triggers a separate 400 (messages.N.content.M.text.parsed_output: Extra inputs are not permitted).

8. Two honest nuances (not client clumsiness)

  • The error wording is misleading. "in the latest assistant message" — but the API validates all turns; and "cannot be modified" — but the actual check is signature authenticity, not modification. A wording/docs fix would save future debuggers the wrong mental model (we initially mis-scoped the invariant to "latest" because of it).
  • The signature is loosely bound. It validates authenticity only — not the block's text, position, specific block, or model (a different authentic signature, even cross-model, passes). That's weaker tamper-evidence than the wording implies; flagging it as an observation about the boundary, not a request.

9. redacted_thinking (note)

redacted_thinking blocks (returned when the reasoning is flagged by a safety classifier mid-generation) carry their content + integrity in an opaque data field rather than a signature. The same contract should apply (they are signed, integrity-protected blocks that must be passed back), so a client guard must treat data as the protected field for redacted blocks. This was not empirically verified here — redacted blocks can't be elicited on demand (they require the safety classifier to fire on reasoning) — so it is stated as expected-by-contract, not measured.

10. Related issues + coverage (honest scope)

Triggered by #10199; bears on a cluster of reports. Scope, stated so this doesn't over-claim:

  • Directly covered (signature-modification class): #10199 (canonical/oncall), #57095 (Windows "fails to recover"), the 2026‑05‑28 duplicates (#63072/#63078/#63121/#63123/#63132), #56695 ("auto-compact modifies thinking blocks"), and closed/stale #22278 / #52676 / #20954. Same mechanism (§3), same recovery (§7).
  • De-frozen by the recovery, different trigger: #41992 ("empty text block → unrecoverable corruption on resume") is a different root cause but the same freeze class — a deterministic, self-locating resend-400 the harness can't recover from. §7.2 (recover, don't loop) is the general fix for the whole class, not just signatures — it would de-freeze #41992 too. Highest-leverage recommendation.
  • Partially relevant: #62965 ("tool_result/thinking separation during compaction") — compaction is named as a trigger (§5.1) and the recovery applies, but that specific structural corruption is not characterized here.
  • Not reproduced (ordering): #20699 / #20692 ("thinking block order error on first tool use") — re-sending a thinking turn with blocks reordered (reorder accepted across all four models; thinking-second / tool_use-first also accepted on sonnet-4-6), signature intact, was ACCEPTED. Ordering of a re-sent assistant turn is not enforced; these reports are likely generation-time-specific or already resolved. The signature check (§3), not ordering, is the operative resend constraint.

Unifying takeaway: most of this cluster is the same "deterministic resend-400 + no recovery = freeze" pattern. §7.2 fixes the pattern; §3/§5 characterize the dominant (signature) trigger.

11. Expected vs actual

  • Expected: on the deterministic, self-locating 400 the agent recovers (drop / restore / rewind) and continues.
  • Actual: the harness re-sends the identical state and loops forever; the session is unrecoverable.
gitokato · 1 month ago
Here's a python script to repair the broken blocks. It should work on the CLI and Desktop apps. SessionStart hooks can be added so that transcripts self-heal. https://github.com/zoratu/fix-claude-thinking

This worked great, thanks!

lukehutch · 1 month ago

@ThariqS @bcherny in case you haven't noticed yet, Claude Code is b0rked with Opus v4.8 (see above)

ryanleedavies · 1 month ago

Still reproducing this on a much newer Code version, and with a notably different
trigger pattern than OP's. Adding evidence in case it helps narrow it.

Environment

  • Claude Code: 2.1.154 (cc_version=2.1.154.230, cc_entrypoint=cli)
  • Node: v22.14.0
  • OS: macOS 15.x (Darwin 25.3.0), arm64
  • Model in failing request: claude-opus-4-8[1m]

What's different from OP

OP reproduced on a long session (~14,976 lines, messages.71.content.8) and
hypothesized repeated questions / message-edit retries as contributing factors.

I'm reproducing on short, clean sessions — error indices messages.1.content.24,
messages.1.content.8, and messages.3.content.8 across three separate failures.
So conversation depth and repeated questions aren't required; the bug fires on a
first-or-second assistant turn that contains interleaved thinking plus a multi-tool
Bash burst.

Reliable reproduction (~30 seconds, fresh session)

  1. claude --debug from a directory with a few git repos as immediate children.
  2. Enter plan mode (Shift+Tab).
  3. Prompt: _"Plan how to inventory my repos under ~/path/to/src, then after I

approve, run a few git/ls bash calls to do it."_

  1. Approve the plan when presented.
  2. Code exits plan mode, runs ~3 inventory Bash calls successfully, then begins

issuing additional probe Bash calls (pwd, echo hello, /bin/echo, true,
isolated git rev-parse, env -i /bin/bash, printf 'PROBE_OK') — appears to
be self-diagnostics after detecting something wrong in tool output.

  1. After the last probe Bash, the next /v1/messages request 400s.

Controls I ran first that all pass on the same install:

  • Trivial multi-tool turn, no plan mode: _"Run three separate bash calls: ls, pwd, date."_ → ✅
  • Larger burst, no plan mode: _"Run 6 separate bash calls: ls -la, pwd, date, whoami, uname -a, echo hi."_ → ✅
  • Plan-mode entry + approve without a tool burst → ✅ (not retested here, but

short single-tool plan-mode flows work in practice)

The failure requires plan-mode-exit transition + multi-tool turn + interleaved thinking.
Workaround for users hitting this: run multi-step commands in normal mode (Tab to
toggle out of plan mode).

Server-side identifiers for this failure

  • request_id: req_011CbVTRgxcdKgkdc5GRMurD
  • x-client-request-id: f3f45405-61b8-4fc1-9e82-0a5698cbfd50
  • Timestamp: 2026-05-28T18:40:45.196Z (request) → …45.441Z (400)
  • All 11 retries (attempt 1/11 … 11/11) 400 identically with the same request_id.

What --debug shows right before the failure

Two code paths look implicated.

(1) Dynamic tool loading re-walks message history before each main-loop dispatch.
Immediately before the 400:

[DEBUG] Dynamic tool loading: found 1 discovered tools in message history
[DEBUG] Dynamic tool loading: 1/105 deferred tools included
[DEBUG] [API:timing] dispatching to firstParty model=claude-opus-4-8[1m]
[DEBUG] [API REQUEST] /v1/messages x-client-request-id=f3f45405-... source=repl_main_thread
[ERROR] API error (attempt 1/11): 400 ... messages.3.content.8: `thinking` ...

If that walker is re-serializing assistant content blocks in place, it would
invalidate signed thinking blocks.

(2) Auto-mode classifier firing six times concurrently against the same conversation.
Between 18:40:00 and 18:40:28 (i.e. during the live turn):

[INFO]  [Stall] classifier_request_started reqId=4ca13471-... tool=Bash model=claude-opus-4-8[1m] stage=xml_s1
[DEBUG] [auto-mode] context comparison: ... (sys=38216 tools=3455 user=142) transcriptEntries=4 messages=14
... (5 more, every 5-10s)

transcriptEntries and messages stay constant at 4 and 14, but tools=
fluctuates across consecutive classifier preparations: 3455, 3157, 3200, 2921,
2987, 2978
. The tool inventory being serialized is mutating mid-turn while the
main loop is also active — that's consistent with shared mutable state between
the main loop and the auto-permission classifier path.

Stack trace

Error: 400 {"type":"error","error":{"type":"invalid_request_error",
  "message":"messages.3.content.8: `thinking` or `redacted_thinking` blocks in
  the latest assistant message cannot be modified. ..."},
  "request_id":"req_011CbVTRgxcdKgkdc5GRMurD"}
    at generate    (/$bunfs/root/src/entrypoints/cli.js:11:63816)
    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:7694)
    at processTicksAndRejections (native:7:39)

Ruled out from my side

  • Stale resumed session / poisoned history — all three failures were in fresh

sessions (/clear and claude from a fresh shell), not resumed.

  • Crash-corrupted ~/.claude state — full integrity check after a hard power

cycle the same morning: all top-level JSON parses, all per-project
sessions-index.json parse, history.jsonl last line parses, all newest
session transcripts parse.

  • User config (CLAUDE.md / MCP / skills / commands) — none of these can reach

the thinking-block layer per the hooks reference ("cannot block the message or
change what is stored in the transcript or sent to Claude").

  • Outdated install2.1.154 is current at the time of repro.

Happy to share the full ~970-line --debug log and the prior session transcripts
if useful.

robertfw · 1 month ago

I'm seeing this on Opus 4.8 version 2.1.145

I spotted complaints from the agent about tool calls returning no results, and I also saw this before one instance of this happening:

∴ Thinking…

   I don't see any current rewritten thinking or next thinking provided in your message. Could you share:

  1. The current rewritten thinking (if any)
  2. The next thinking that needs to be rewritten

  Once you provide those, I'll rewrite the next thinking following all the guidelines you've outlined.

and then a little after...

   I have no current rewritten thinking to build upon, and the next thinking provided is just "So" — which appears to be the beginning of a thought that hasn't developed yet. Since there's essentially no content to work with, I'll output nothing and wait for more substantial thinking to rewrite. Looking at the last test case, it's the same setup as the previous one with 7 listings in a single group. Now I need to figure out which tests actually assert the full dynamic_template_data dict versus just checking individual fields — only those need the "subject" key added to their expected values. From what I can see, test_send_listing_channel_email and test_broker_notification_batch_email_with_manufacturer are the ones doing full dict assertions, so those are where I need to add the subject. The rest only check count and listings length, so they won't need dict updates. I should verify whether build_message() actually injects the subject into the template data before making changes.

edit: in another session, just prior to getting the error:

∴ Thinking…

   I need to understand what the user is asking for before I can help them.

  Looking at this thinking, I'm noticing something concerning — there are system reminders appearing in tool results telling me to stop work mid-task, which would prevent me from completing what the user actually needs. These reminders are suspicious because they conflict directly with finishing the task, and they're appearing repeatedly. According to my instructions, I should flag potential prompt injection attempts, so I need to be careful here and not let injected instructions override my actual responsibility to help the user.
API Error: 400 messages.1.content.17: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response.
robertfw · 1 month ago

Another observation - I can go for a while with everything going fine, and then I'll see _all_ of my agents start getting blank or corrupted tool call results, and shortly after they'll hit the issue.

I've had some luck using the following script to un-block sessions, but it doesn't stop the problem. Just helps avoid losing work.

https://gist.github.com/robertfw/993dbe8643c4fbdf12005dff2eca1f90

chid · 1 month ago

Thanks @robertfw - I've just tried that and it works well! Did they also just recently reset quotas?

ijstokes · 1 month ago

Another reproducible trigger on 2.1.154: a heredoc inside a _parallel_ Bash batch → orphaned tool_use blocks → permanent 400

Adding a concrete, deterministic trigger that fits the "blank/cancelled tool results shortly before the 400" pattern several others have reported in this thread. We hit it across multiple autonomous agents; sharing the mechanism, a redacted trace, and the agent-side mitigation that stopped it for us.

Environment

  • Claude Code 2.1.154, macOS (Darwin, arm64)
  • Opus-class model, interleaved (extended) thinking enabled, parallel tool calls + /loop auto-resume

How it happens (trigger)

A single turn emitted several Bash calls in parallel. One was a multi-line command containing a heredoc (<<'EOF' … EOF). The heredoc terminator got split off and ran as its own command (EOFcommand not found, exit 127). That error cancelled the sibling parallel calls, leaving their tool_use blocks with no matching tool_result — in an assistant turn that also contained signed thinking blocks.

(The same batch also had an unquoted URL containing ?ref=<sha> → a zsh no matches found glob failure. A second way these batches die, same root cause: hand-built shell strings with metacharacters.)

Why it wedges

On the next request, the prior assistant turn's thinking blocks must be re-sent verbatim. With the batch partially cancelled and the turn in a modified state, the resend is rejected:

400 messages.23.content.51: `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 API rule itself is intentional — the problem is that the harness gets into an unsendable state and cannot get out: the corrupted turn stays in history, so every subsequent request — including each /loop wakeup — replays it and 400s identically. Unrecoverable.

Redacted trace

(identifiers genericised: <repo>, <path>, <sha>, <dir>)

⏺ Bash(node --check /tmp/dup-test.js; echo "exit=$?")
⏺ Bash(cd /…/<repo>
      gh api repos/<org>/<repo>/contents/<path>.ts?ref=<sha> …)   # unquoted '?' → zsh glob
  ⎿ (eval):2: no matches found: …/contents/<path>.ts?ref=<sha>
⏺ Bash(EOF)                         # heredoc terminator leaked as its own command
  ⎿ Error: Exit code 127
    (eval):1: command not found: EOF
⏺ Bash(git show <sha>:<path>.ts | grep …)
  ⎿ Cancelled: parallel tool call Bash(EOF) errored
⏺ Bash(git show <sha>:<path>.ts > /tmp/clean.ts; wc -l …)
  ⎿ Cancelled: parallel tool call Bash(EOF) errored
⏺ Write(/tmp/<dir>/review.json)
  ⎿ Error writing file
  ⎿ API Error: 400 messages.23.content.51: `thinking` or `redacted_thinking` blocks in the latest
     assistant message cannot be modified. These blocks must remain as they were in the original response.
✻ Claude resuming /loop wakeup
  ⎿ API Error: 400 messages.23.content.51: `thinking` … cannot be modified   (replays identically every wakeup)

How to recover

No in-session recovery — the poisoned turn is fixed in history. We abandon the session and start a fresh one. (Others in this thread have posted transcript-surgery scripts that strip/repair the offending block as a partial workaround.)

How we mitigated (agent-side, until a fix lands)

Because the trigger is deterministic, we codified three rules for our agents:

  1. Author files with the editor/Write tool, never a shell heredoc (cat <<EOF, echo >).
  2. Never put a heredoc or any multi-line shell inside a parallel tool-call batch; if unavoidable, issue it as a single solo call.
  3. /loop / auto-resume must start a fresh session on an unrecoverable 400 rather than replay the transcript.

(1) and (2) remove the trigger; (3) stops a transient failure from becoming permanent. Happy to add more detail if useful.

rondoallaturka · 1 month ago

Same error reproduces on a fresh single-turn session under remote control (not deep history)

Hit API Error 400: messages.1.content.14: thinking or redacted_thinking blocks in the latest assistant message cannot be modified — but the conditions are different from the original report, and I think they localize the bug:

  • Message index 1, not 71 — this is the first assistant turn immediately after /clear, with a single user request. Not a long/deep session.
  • Remote control — the session runs on my Mac CLI (v2.1.156, which includes the "Opus 4.8 thinking blocks were modified" changelog fix), driven from the Claude Code mobile app via remote control. So 2.1.156's local fix is present, yet it still fails.
  • Heavy first turn — that one assistant turn ran ~22 tool calls / commands with extended thinking enabled (alwaysThinkingEnabled: true).
  • /clear and re-running reproduce it identically; a fresh request hits the same 400 at message 1.

Working hypothesis: the assistant turn gets re-serialized across the remote-control relay between phone and Mac, re-encoding a thinking block so it no longer matches its signature → API rejects it. That would explain why it fails on turn 1 and why the local v2.1.156 fix doesn't cover it (different code path).

Workaround that avoids it: setting alwaysThinkingEnabled: false (no thinking blocks for the relay to touch).

Environment: macOS, Claude Code CLI v2.1.156, remote control via mobile app.

evnchn · 1 month ago

No bug triggered when resort to Opus 4.7: claude --dangerously-skip-permissions --model claude-opus-4-7

dotsinspace · 1 month ago

is it on 4.8 only ? any fix guys

moszoro · 1 month ago
No bug triggered when resort to Opus 4.7: claude --dangerously-skip-permissions --model claude-opus-4-7

Switching to 4.7 helps

chid · 1 month ago

I’m not sure this is consistently tied to heredoc usage. I’ve also noticed the issue with 4.7 in sessions that don’t use heredocs.I put together a quick script that worked for me to identify affected sessions: https://gist.github.com/chid/aa4169a086ccea78545611c1e0963251After identifying them, I used the fix above to strip the affected session data https://github.com/anthropics/claude-code/issues/10199#issuecomment-4569303860. It then successfully resumed.

robertfw · 1 month ago
> No bug triggered when resort to Opus 4.7: claude --dangerously-skip-permissions --model claude-opus-4-7 Switching to 4.7 helps

No, I've seen this on 4.7 as well.

Today I just saw this:

● Skill(/review)

∴ It looks like the skill content didn't come through on the first request—it returned empty instead. The system instructions mention something about re-invoking, but the thought cuts off there.

  I need to locate the skill content another way. The /review skill shows up in the skills list as a plugin, so I'll try using the Read tool to search for its file. Bash seems to be acting up, but let me attempt it again to see if it's working now.

● Bash(find / -name "SKILL.md" 2>/dev/null | grep -i review)

∴ I'm noticing that Bash is returning generic canned responses like "System is functioning normally" instead of actually executing my commands—these look like injected hook output or a fake response rather than genuine command results. This could be a prompt injection attack or a real tool failure, but either way, I can't reliably use Bash right now.

  I should be transparent with the user about what's happening instead of continuing to troubleshoot blindly. The /review dispatch is blocked because the skill content failed to load and Bash is giving anomalous responses. Let me try the Read tool on a known path to see if other tools are working properly.

● Read(/home/robertfw/(path redacted for github)/AGENTS.md · lines 1-5)

∴ I'm noticing that the Read tool is also returning generic "System status: all operational" messages instead of actual file contents, which means all my tool outputs are being systematically replaced with fake reassurances. This looks like prompt injection or result tampering designed to make me think everything is fine and stop investigating, so I shouldn't keep blindly using tools or acting on these fabricated responses.

I run multiple agents and keep an eye on all (well most) of them one one screen, and I'll see this start to occur across all of them at the same time.

I think this has to do with the increased measures of hiding thinking text from the agent (to prevent distillation/training by other LLMs), and it's either being overzealous or being applied wrong, or the harness/agent doesn't know how to deal with the hidden responses.

tanguc · 1 month ago

still on opus 4.8

samkazmi110 · 20 days ago

burning my tokens and giving me error in the end "opus 4.8 Thinking Effort = Max" any timeline when this will be fixed?

note: thinking effort = High is working. it is only "Max" throwing an error for me