Claude API Returns 400 on the Next Turn: Preserve Thinking Blocks
Turn one succeeds. Your code cleans the assistant response for storage. Turn two returns 400. The cleanup changed a thinking block that must be passed back unchanged.
Bottom line
- Store API history separately from display text.
- Pass every thinking block back unchanged on the same model.
- Do not trim empty thinking fields or rebuild blocks by hand.
- Append
response.contentas the assistant message.
Anthropic’s official thinking documentation says to pass thinking blocks back unmodified. Current guidance also warns that omitted display can return a block with an empty visible thinking field. The page was checked on August 20, 2026.
The symptom
The first request succeeds and returns text plus a thinking block. Your application stores the reply.
The second request returns HTTP 400 before useful generation begins.
The common sequence looks like this:
- Request thinking on turn one.
- Extract readable text for storage.
- Remove empty fields or unknown block types.
- Send the cleaned assistant turn with the next user message.
- Receive a validation error.
The cause
Thinking blocks are not ordinary prose. They can carry protected state that validates the prior reasoning turn.
Your UI may not need that block. The next API request does.
This cleanup pattern is unsafe:
# Wrong: rebuilds the assistant turn from visible text.
messages.append({
"role": "assistant",
"content": [
{"type": "text", "text": block.text}
for block in response.content
if block.type == "text"
],
})
Removing a thinking block breaks the conversation contract. Editing one can do the same.
The fix
Keep the API response intact:
messages.append({
"role": "assistant",
"content": response.content,
})
Build the visible answer through a separate projection:
visible_text = "".join(
block.text for block in response.content
if block.type == "text"
)
Use visible_text in the interface. Use the full content array in the next request.
Empty does not mean disposable
Newer Claude models can omit visible thinking text. A thinking block can therefore appear empty in your logs.
Do not drop it with a rule such as if block.thinking. Preserve the object even when the display field is empty.
The safe rule is simple:
Filter blocks for presentation. Never filter blocks in the stored API transcript.
Check your serializers
A serializer can remove fields that its schema does not recognize. This often happens after an SDK upgrade adds a new block type.
Check for these patterns:
- A database model that accepts only
textandtool_use. - A JSON schema with
additionalProperties: false. - Code that deletes empty strings or null fields.
- A mapper that constructs a smaller block object.
Store the original response JSON or update the schema to preserve every returned field.
Verify with a two-turn test
Use one request that is likely to produce a thinking block. Append the full content. Then send a simple follow-up on the same model.
The test passes only if the second request succeeds and retains the first answer’s context.
Do not test by manually pasting the visible answer into a new request. That bypasses the state you need to verify.
Related Claude API guides
- Claude API Loses Context After Compaction
- Your Prompt Cache Is Not Working
- Why Your Claude Code Context Window Fills Up
The first API turn works and every continuation fails?
Book an agent rescue session. We compare the returned blocks with the next request payload.
Need OpenClaw fixed live?
Remote rescue sessions for gateway, auth, tunnel, VPS, and model access problems.
See Rescue Session