Session JSONL corruption from non-atomic writes during concurrent tool execution
Bug Description
Claude Code sessions become permanently unresumable with API Error: 400 due to tool use concurrency issues when the JSONL session file is partially written during concurrent tool execution.
Steps to Reproduce
- Start a session with multiple concurrent tool calls (3+ parallel MCP tool calls or subagent dispatches)
- While tools are executing, trigger a state change (enter plan mode via Shift+Tab, or send a new message)
- The session hits 400 and cannot continue.
/rewindsometimes helps but often the corruption persists.
Root Cause (Inference)
The session file appears to be written non-atomically. When multiple tool results are being written concurrently, one entry can be dropped — leaving a tool_use in the assistant message with no matching tool_result in the user message. Every subsequent API call fails validation.
Evidence from affected sessions:
- Assistant message contains 3
tool_useblocks - Following user message contains only 2
tool_resultblocks - The missing
tool_resultwas never written to the JSONL - The orphaned
tool_useID has no matching result anywhere in the file
Environment
- Claude Code CLI: 2.1.85 (also reproduced on 2.1.89-2.1.91)
- macOS 15.x (Darwin 25.4.0)
- Reproduction rate: approximately 1 in 10 sessions with heavy tool use
- Higher rate when using MCP tools (external services, database connectors)
Suggested Fix
Use atomic write pattern for session persistence:
1. Write session data to a temporary file (same directory)
2. fsync the temporary file
3. Rename temporary file to the session file (atomic on POSIX)
This is a standard pattern for crash-safe file updates. The current approach of writing directly to the session file creates a window where interruption leaves the file in an inconsistent state.
Workaround
Manually repair the JSONL by:
- Finding the orphaned
tool_result(parent UUID points to missing entry) - Injecting a synthetic
tool_resultfor the orphanedtool_use - Resuming the session
This is fragile and requires understanding the JSONL format.
Related Issues
- #31328 (session becomes unresumable after JSONL writer drops assistant entry — closed as stale)
- #21321 (tool-use concurrency 400 mid-session)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗