Claude API Loses Context After Compaction: Round-Trip the Block
Your agent works for many turns, crosses the compaction point, and suddenly acts as if the earlier task never happened. The API created the summary. Your message loop threw it away.
Bottom line
- Claude returns compaction state as a content block.
- Saving only
block.textdeletes that state. - Append the complete
response.contentarray to your message history. - Test the boundary with a low development trigger and inspect block types.
Anthropic’s compaction documentation says clients must pass the compaction block back on later requests. It recommends appending the entire response content. The page was checked on August 20, 2026.
The symptom
The agent keeps its instructions and tool state for many turns. Then its behavior changes near the context limit:
- It asks for information already supplied.
- It repeats completed work.
- It forgets the current acceptance criteria.
- It stops referring to an earlier tool result that the summary should preserve.
The API call still succeeds. There is no context-window error.
The cause
Compaction replaces older history with a summary inside a compaction content block.
Many message loops keep only visible text:
# Wrong: discards compaction and other non-text blocks.
assistant_text = "".join(
block.text for block in response.content
if block.type == "text"
)
messages.append({"role": "assistant", "content": assistant_text})
That code works until the API returns a non-text block that carries state. The next request contains readable prose but no compaction summary.
The model did not forget the state. The client removed it.
The fix
Append the full content array:
messages.append({
"role": "assistant",
"content": response.content,
})
In TypeScript:
messages.push({
role: 'assistant',
content: response.content,
});
Do not serialize each block into a custom text format. Preserve the typed objects that the SDK returned.
Add a boundary test
Log only block types, not private content:
print([block.type for block in response.content])
When compaction occurs, the output includes compaction before later response blocks.
Add an assertion to your loop test:
returned_types = [block.type for block in response.content]
if "compaction" in returned_types:
messages.append({"role": "assistant", "content": response.content})
saved_types = [block.type for block in messages[-1]["content"]]
assert "compaction" in saved_types
The test proves that your storage layer did not flatten the response.
Check every storage boundary
The in-memory append can be correct while persistence still breaks it.
Inspect these points:
- The SDK response object.
- The object written to your database or session file.
- The object loaded for the next request.
- The final
messagespayload.
At each point, the compaction block must remain typed and unchanged.
Do not patch the symptom with a larger context window
A larger window delays the boundary. It does not fix a message loop that deletes state.
Repair the round trip first. Then choose a compaction trigger that matches the workload.
Related context guides
- Why Your Claude Code Context Window Fills Up
- Restart Claude Code Sessions at 200K Tokens
- Your Prompt Cache Is Not Working
Your agent survives short tests but loses the task overnight?
Book an agent rescue session. We inspect the stored message blocks at the failure boundary.
Need OpenClaw fixed live?
Remote rescue sessions for gateway, auth, tunnel, VPS, and model access problems.
See Rescue Session