[BUG] Conversation Compaction Corrupts Tool Use/Result Pairs
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?
Description
After conversation compaction occurs, subsequent API calls fail with a 400 error indicating orphaned tool_result blocks without corresponding tool_use blocks in the previous message.
Error Message
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.2.content.3: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01Mhh3CD3BeVEU2mUPenc1X3. Each `tool_result` block must have a corresponding `tool_use` block in the previous message."},"request_id":"req_011CTeiXF7oECCgLbvci5oey"}
Steps to Reproduce
- Start a Claude Code conversation
- Perform many tool uses (Read, Write, Bash, etc.) over an extended conversation
- Wait for automatic conversation compaction to trigger (likely based on token threshold)
- After compaction, attempt to continue the conversation
- Observe API 400 error about mismatched tool_use_id
Expected Behavior
Conversation compaction should maintain the integrity of tool_use/tool_result pairs by either:
- Removing both the
tool_useAND correspondingtool_resultblocks together, OR - Keeping both blocks intact
Actual Behavior
Compaction appears to remove tool_use blocks while leaving orphaned tool_result blocks, which violates the Claude API requirement that every tool_result must have a corresponding tool_use in the previous message.
Impact
- Forces users to start a new conversation, losing context and flow
- Conversation history becomes corrupted and unusable
- Disrupts multi-step workflows and long-running tasks
Workaround
Start a new conversation when this error occurs. There is no way to recover the corrupted conversation.
Environment
- Claude Code Version: [Current version]
- Platform: Linux
- OS Version: Linux 6.8.12-11-pve
- Date Observed: 2025-09-30
Additional Context
This issue appears to be related to the conversation compaction algorithm that reduces token usage. The compaction logic needs to ensure atomic removal/retention of tool_use/tool_result pairs to maintain API contract compliance.
Suggested Fix
The compaction algorithm should:
- Identify tool_use/tool_result pairs by matching tool_use_id
- Remove pairs atomically (both blocks together or neither)
- Validate message structure after compaction to ensure no orphaned tool_result blocks remain
What Should Happen?
Conversation compaction should maintain the integrity of tool_use/tool_result pairs. When compacting a conversation to reduce token usage, the algorithm must either:
- Remove both the tool_use block AND its corresponding tool_result block atomically (as a matched pair), OR
- Keep both blocks intact
After compaction, the conversation should remain valid and usable, with all tool_result blocks having their corresponding tool_use blocks in the previous message. The conversation should continue without API errors.
Error Messages/Logs
Steps to Reproduce
- Start a new Claude Code conversation
- Perform multiple tasks that require many tool uses (Read, Write, Bash, Edit, etc.) - approximately 50+ tool calls
- Continue the conversation until it reaches the token threshold that triggers automatic conversation compaction
- After compaction occurs (you may see a brief pause or indication), attempt to continue the conversation with another request
- Observe the API 400 error: "unexpected
tool_use_idfound intool_resultblocks: [id]. Eachtool_resultblock must have a correspondingtool_useblock in the previous message"
Note: The exact token threshold for compaction is unknown, but this typically occurs in longer conversations with extensive tool usage.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
Claude Code version 2.0.1
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
_No response_
14 Comments
This always happens to me after leaving a thread open for the night and trying to continue the next day
This has bene happening multiple times for me.
" numbers.
⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.128.content.4: unexpected
tool_use_idfound intool_resultblocks: toolu_01PMwVK9r2AmDuVewMC6X5oP. Eachtool_resultblock must have acorresponding
tool_useblock in the previous message."},"request_id":"req_011CVmBBkUgwq3XxKSegv8ZK"}⎿ Error submitting feedback / bug report
⎿ Error submitting feedback / bug report
⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.128.content.4: unexpected
tool_use_idfound intool_resultblocks: toolu_01PMwVK9r2AmDuVewMC6X5oP. Eachtool_resultblock must have acorresponding
tool_useblock in the previous message."},"request_id":"req_011CVmBGALkNw57XQBv1134G"}⎿ Catch you later!
⎿ Error submitting feedback / bug report
∴ Thought for 4s (ctrl+o to show thinking)
✶ Creating branch… (esc to interrupt · ctrl"
I can work around it by exiting, and then restarting claude with the previous session
Just happened with me too, not sure how I triggered it. It as in the middle of a conversation, I pasted and image and then happened, even going back and editing the message or running compact didn't help
⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.132.content.2: unexpected
tool_use_idfound intool_resultblocks: toolu_015PWr6hFQar3eB4asii29TF. Eachtool_resultblock must have atool_usecorresponding
block in the previous message."},"request_id":"req_011CW6QCC1gAKZocWEwU4yyr"}Same happened to me after I stopped CC with ESC during its work (wanted to adjust the instructions)
This just happened to me, It's annoying after sending a prompt off before going out for the day, when I returned the session is unusable - even if I try /resume or start claude with --continue.
This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.
Additional Reproduction Report - macOS / Claude Code 2.1.25
I'm experiencing this same issue reproducibly. Adding data points from a different environment:
Environment
Reproduction Pattern
The crash occurs in tool-heavy sessions involving:
In my case, the session was performing mobile app development work: running tests, reading/editing source files, and executing CLI tools (simulator commands, test runners). The crash occurred approximately 20 minutes into the session after ~50+ tool invocations.
Exact Error
Observations
Impact
Each crash loses all session context, requiring manual reconstruction of work state. For multi-step workflows (debugging, refactoring, test automation), this is significantly disruptive.
Confirming the diagnosis
The error message clearly indicates orphaned
tool_resultblocks, matching the original report's analysis that compaction is removingtool_useblocks without their correspondingtool_resultpairs.---
Adding this report to help prevent autoclose - this is an active, reproducible bug affecting multiple platforms.
This bug also occurs in the Claude by Anthropic Excel add-in. When conversations get long enough to trigger auto-compaction, the same error appears:
unexpected tool_use_id found in code_execution_tool_result blocks: each code_execution_tool_result block must have a corresponding server_tool_use block before itThe compaction process corrupts the tool_use/tool_result pairs, making the conversation unusable. The only workaround is starting a new conversation.
I'm having the same issue with the Claude Excel add-in. After ussing multiple tool calls (+200) it triggers auto-compaction and after that it throws the same
unexpected tool_use_id... error, making the conversation unusable. It prevents me from working since i necesarilly need to perform those reads.Background Task Variant — JSONL-Verified Root Cause Analysis
I've confirmed this bug via direct JSONL forensics on a corrupted session. My case involves a specific trigger not yet documented in this thread: a long-running background Bash task that outlives context compaction.
---
Environment
| | |
|---|---|
| Claude Code | 2.1.49 |
| Model | claude-opus-4-5-20251101 |
| Platform | Linux 6.12.69-1-t2-noble (Ubuntu 24.04) |
| Session ID |
7ee0cb6e-7458-4d10-88af-1de5a9b11e3e|| Session slug |
sunny-nibbling-locket|| JSONL lines | 5,458 total |
| Tool calls in session | 431
tool_use/ 429tool_result|---
Exact Sequence of Events (with JSONL line numbers)
---
The Bug
The background task was enqueued at 12:51:22Z with a
sleep 300(5 minute wait). Compaction fired at 12:56:53Z — 5 minutes 31 seconds later — and replaced the entire pre-compaction history with a summary. 39 milliseconds later, the background task completed and injected itstool_resultinto the post-compaction message array.Post-compaction, the API received this message sequence:
The
tool_usethat spawned this background task (line 4920) was in the pre-compaction history. Compaction replaced everything before line 5353 with a text summary. But the background task'stool_resultarrived after compaction and was appended to the post-compaction array — where no matchingtool_useexists.---
Why Background Tasks Make This Worse
Most reports in this thread describe compaction breaking tool pairs from completed tool calls (both
tool_useandtool_resultalready in history). That's a serialization bug in the compaction algorithm.The background task variant is a race condition:
tool_useis emitted → goes into message historytool_usetool_resultinjected into new post-compaction arraytool_useexists → permanent 400 errorThis means any background task with sufficient duration will deterministically trigger this bug in long sessions, because compaction cannot know about in-flight background tasks.
---
Suggested Fix (Background Task Variant)
The compaction/message-assembly layer needs to be aware of in-flight background tasks:
tool_use_ids. Either:tool_useblocks in the compacted output, ORtool_useis not in the current message array (i.e., was compacted away), silently drop thetool_resultinstead of injecting ittool_resultblocks and strip them before sending to the APIOption (2) is probably the safest — a stale background result from a compacted context is unlikely to be useful anyway.
---
Reproduction Steps
sleep 300 && echo done(timeout: 360000)The key is that the background task must complete after compaction fires. A 5-minute sleep in a busy session reliably triggers this.
Recovery Procedure — How to Fix a Corrupted Session
Following up on my previous comment with the background task race condition analysis: I successfully recovered the corrupted session by removing the orphaned
tool_resultline from the JSONL file. Here's the full procedure so others can do the same.---
Overview
When this bug strikes, a
tool_resultblock in the JSONL references atool_use_idthat was wiped by compaction. The fix is:tool_resultline in the JSONLYou keep all your post-compaction work (tool calls, edits, reasoning) — in my case, ~100 lines of successful interactions that happened after the orphan was introduced.
---
Step-by-Step
1. Locate the JSONL file
Session files live in
~/.claude/projects/<project-slug>/<session-uuid>.jsonl. If you know the session UUID (shown in the error or viaclaude --recent), go directly to it:2. Find the orphaned tool_use_id
Extract the ID from the error message. It looks like:
3. Scan the JSONL for the orphan
4. Validate there are no other orphans
5. Back up and fix
6. Resume the session
---
One-Shot Prompt for Claude Code
If you hit this error and want Claude to diagnose and fix it for you, start a new Claude session (the broken one can't accept input) and paste this:
---
JSONL Conversation Parser Tool (optional but useful)
I published a standalone JSONL parser for Claude Code conversation files as a gist: claude_conversation_parser.py
It does full-text search across all conversation content, date filtering, project filtering, stats, and markdown export. Useful for finding the broken session if you don't have the UUID handy — though for the actual JSONL surgery you need the raw line-level access shown above.
Install
Usage
If you want Claude to remember this tool exists across sessions, add to your auto-memory (
~/.claude/projects/<project>/memory/MEMORY.md):---
Results
This procedure recovered my session fully. The ~100 lines of post-compaction work (tool calls, file edits, reasoning context) were preserved. Resumed with
claude --resume 7ee0cb6eand continued working immediately — no permission re-grants needed, no context re-explanation.Still happening on v2.1.62 (Windows 11, Feb 2026)
Confirming this bug is still present. Hit the exact same error on Claude Code 2.1.62 with Claude Opus 4.6 on Windows 11 in a multi-workspace session (4 working directories).
req_011CYZRwNgUrsUV2CmMH66nH87bbc5f8-3bc3-435f-8e22-4cd2e1563c6c---
Workarounds Found
Option 1: Fork the conversation (easiest)
Fork the conversation from a point before the corruption. This creates a new session from a valid checkpoint and avoids the corrupted messages. This worked immediately.
Option 2: Manual JSONL surgery
Edit the session JSONL file to remove the orphaned tool_use/tool_result pair:
``
``~/.claude/projects/<project-slug>/<session-id>.jsonl
tool_use_idfrom the error message:``
bash
``grep -n "toolu_XXXXX" <session-file>.jsonl
tool_use(assistant message), an optionalprogressline, and thetool_result(user message). Inspect with:``
bash
``# Check uuid/parentUuid chain around the affected lines
sed -n '5p;6p;7p;8p;9p' <file>.jsonl | python3 -c "
import sys, json
for line in sys.stdin:
d = json.loads(line)
print(f'type={d.get(\"type\")}, uuid={d.get(\"uuid\")}, parent={d.get(\"parentUuid\")}')
"
parentUuidchain:```python
import json
filepath = '<session-file>.jsonl'
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()
# remove_indices = the 0-indexed line numbers of tool_use, progress, tool_result
# next_line_index = the line after tool_result
# parent_uuid = uuid of the line BEFORE the tool_use
remove_indices = {5, 6, 7} # adjust to your lines
next_line_index = 8
parent_uuid = '<uuid-of-line-before-tool_use>'
new_lines = []
for i, line in enumerate(lines):
if i in remove_indices:
continue
if i == next_line_index:
obj = json.loads(line)
obj['parentUuid'] = parent_uuid
new_lines.append(json.dumps(obj, ensure_ascii=False) + '\n')
else:
new_lines.append(line)
with open(filepath, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
```
claude --resume <session-id>Note: In our testing, JSONL surgery alone did not fix
--resume, but forking the conversation worked immediately. The resume path may reconstruct messages differently than fork.---
Closing my duplicate issue #29447 in favor of this one.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.