Today, while I was deep in the middle of handing off some work between Claude Code sessions, I got this weird error out of nowhere:
API Error: 400 messages.7.content.3: `thinking` or `redacted_thinking`
blocks in the latest assistant message cannot be modified. These blocks
must remain as they were in the original response.
I’d never seen it before, and at first nothing I did made it go away — every time I thought I’d fixed it, it came right back, sometimes pointing at a different spot in the conversation. So I went down the rabbit hole, figured out what was actually happening, and I’m writing it up here in case you hit the same wall. The good news: it’s almost always fixable, and you usually don’t lose your work.
What the error means
When thinking is enabled, Claude’s responses include thinking (or redacted_thinking) blocks. These carry a cryptographic signature, and the API requires them to be passed back byte-for-byte unchanged — same text, same signature, same position in the message. If a thinking block gets altered, reordered, truncated, or loses its signature, the next API call is rejected with a 400 pointing at the offending message and content index (e.g. messages.7.content.3).
In Claude Code, this almost never comes from anything you typed. It comes from the session history file the CLI replays on each turn.
Two flavours of this bug (figuring out which you have)
This was the part that tripped me up, so it’s worth slowing down on. There are two versions, and they need different fixes.
1. A fixed error index that never moves. The same messages.N.content.M every time. This is classic history corruption — one bad block sitting in your session file, often from an interrupted turn, a crash, or switching models/thinking settings mid-conversation. Cleaning the file fixes it.
2. An error index that keeps moving (7.content.3, then 1.content.18, then 3.content.41…). This means the bad block is being regenerated on every turn, not sitting statically in old history. The usual cause is a parallel-tool-call cascade: Claude fires several tool calls at once, one gets cancelled, and the turn ends up with thinking blocks tangled up with orphaned/cancelled tool calls. Cleaning the file only unblocks you until the session re-enters the same spiral. The durable fix is to not resume that session.
The fixes, in order
Step 0 — Update Claude Code first
Several fixes have shipped for exactly this class of bug (thinking blocks orphaned by interrupted or parallel tool calls on replay).
claude update
# or
npm install -g @anthropic-ai/claude-code
Step 1 — The easy escape hatch: clear or compact
If you don’t need to preserve the conversation, /clear removes the bad block entirely by starting fresh. If you want to keep your thread of work, /compact summarises the history into a clean context and drops the corrupt block. Both are one command and solve the majority of cases.
Step 2 — Turn thinking off (fastest unblock)
If the API isn’t enforcing signature integrity, the malformed block stops being rejected. Toggle thinking off (via /configor your version’s thinking toggle) to get moving immediately. You can turn it back on later.
Step 3 — Repair the session file in place
If you’re mid-handoff and can’t clear, you can surgically remove thinking blocks from the session file. The single most important rule: fully quit Claude Code before editing. While the process is alive, it owns the file and will overwrite your edits from memory on the next turn — re-introducing the exact block you just removed. This is the number-one reason people think the fix “didn’t work.”
# 1. Make sure nothing is running
pkill -f claude; sleep 1; pgrep -fl claude # should print nothing
# 2. Find your project's session directory
ls -lt ~/.claude/projects/<your-project-hash>/
# 3. See which session files still contain thinking blocks
for F in ~/.claude/projects/<your-project-hash>/*.jsonl; do
echo "$(grep -c '"thinking"' "$F") $F"
done
Then strip thinking blocks from the affected file(s). This Python snippet removes them and drops any assistant turn left empty afterward (the API rejects empty assistant messages too):
F="path/to/session.jsonl"
cp "$F" "$F.bak" # always back up
python3 - "$F" <<'EOF'
import json, sys
path = sys.argv[1]
out, removed, dropped = [], 0, 0
for ln in open(path).read().splitlines():
if not ln.strip(): continue
try: obj = json.loads(ln)
except json.JSONDecodeError:
out.append(ln); continue
if obj.get("type") == "assistant":
c = obj.get("message", {}).get("content")
if isinstance(c, list):
new = [b for b in c
if not (isinstance(b, dict)
and b.get("type") in ("thinking", "redacted_thinking"))]
removed += len(c) - len(new)
if len(new) == 0:
dropped += 1
continue
obj["message"]["content"] = new
out.append(json.dumps(obj, ensure_ascii=False))
open(path, "w").write("\n".join(out) + "\n")
print(f"removed {removed} thinking block(s), dropped {dropped} empty turn(s)")
EOF
Removing a whole thinking block is allowed — the API only objects to modified ones. After editing, verify it’s clean and stays clean, then relaunch:
grep -c '"thinking"' "$F" # expect 0
A few gotchas worth knowing
- Your projects can have many session files. This one got me. Claude Code resumes the most-recently-modified one, and
--continueauto-picks by timestamp. I kept cleaning a file and the error kept coming — turns out I was editing the wrong session. Check every.jsonlin the project directory, not just the obvious one. - A moving error index = a recurring trigger. Don’t just clean the file; avoid resuming the session that keeps producing the bad turn. Start fresh from a handoff doc or
/compactinstead. - The error path is an API-message index, not a JSONL line number. The history file includes user turns, tool results and summaries that get filtered/renumbered before the request is built, so
messages.7is rarely line 7 of the file. This is why blanket-cleaning all assistant turns is more reliable than hand-editing one line.
TL;DR (what I wish I’d known at the start)
The thinking-block 400 is a session-state problem in Claude Code, not your data. Update the CLI, then /clear or /compact if you can. If you have to preserve the session, quit Claude Code completely before touching anything, strip the thinking blocks from the session JSONL, verify it’s clean, and relaunch. And if the error index keeps moving between attempts, stop trying to resume that session — the bad turn is being regenerated each time, so just start fresh from your handoff notes.
That last point is what finally got me unstuck. Hope it saves you the afternoon it cost me.
