400 error: tool_result without matching tool_use after context compression
Bug Report
Description
During a long session, context compression appears to drop tool_use blocks while keeping their corresponding tool_result responses. This causes all subsequent API calls to fail with a 400 error:
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.0.content.0: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01BCViQJcD7bjkgs8tr5qAHR. Each `tool_result` block must have a corresponding `tool_use` block in the previous message."}}
Environment
- Claude Code version: 2.1.62
- Model: Claude Opus 4.6 (
claude-opus-4-6) - Platform: Windows 11 Pro (10.0.26200)
- Shell: Git Bash
- Session ID:
87bbc5f8-3bc3-435f-8e22-4cd2e1563c6c - Request ID:
req_011CYZRwNgUrsUV2CmMH66nH - Orphaned tool_use_id:
toolu_01BCViQJcD7bjkgs8tr5qAHR - Session length: ~136 log entries (multi-workspace session with heavy tool usage)
- Multi-workspace: Yes (4 working directories active)
Steps to Reproduce
- Have a long-running session with many tool calls (especially with multiple workspaces)
- Context compression kicks in to stay within the context window
- A
tool_useblock gets removed during compression but its correspondingtool_resultremains - All subsequent messages fail with 400
invalid_request_error - Session becomes unrecoverable — only
/clearor new session works claude --resume <session-id>also fails with the same error since the corrupted history is persisted in the JSONL file
Expected Behavior
Context compression should ensure tool_use / tool_result pairs are always kept or removed together, maintaining valid message structure per the API contract.
Actual Behavior
After compression, a tool_result block references toolu_01BCViQJcD7bjkgs8tr5qAHR but the corresponding tool_use block no longer exists in the message history. The API correctly rejects this as invalid, but the client should have prevented it.
Workaround
Option 1 (easy): Fork the conversation — this creates a new session from a valid checkpoint and avoids the corrupted messages.
Option 2 (manual JSONL surgery): Edit the session JSONL file directly to remove the orphaned tool_use/tool_result pair:
- Locate the session file:
````
~/.claude/projects/<project-slug>/<session-id>.jsonl
- Search for the orphaned
tool_use_idfrom the error message:
````
grep -n "toolu_01BCViQJcD7bjkgs8tr5qAHR" <session-file>.jsonl
- This will show 2-3 lines: the
tool_use(assistant message), an optionalprogressline, and thetool_result(user message). Note their line numbers and UUIDs.
- Back up the file, then use a script to:
- Remove the
tool_use,progress, andtool_resultlines - Update the next line's
parentUuidto point to the message before the removedtool_use, keeping the chain intact
- Resume the session with
claude --resume <session-id>
Note: In our testing, Option 2 (JSONL editing) fixed the file but --resume still failed. However, forking the conversation (Option 1) worked immediately. This suggests the resume path may reconstruct messages differently than fork.
Suggested Fix
The context compression logic should treat tool_use + tool_result as atomic pairs — if one is removed, both must be removed. Additionally, a validation pass before sending to the API could catch and strip any orphaned tool_result blocks.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗