Race condition in ensureToolResultPairing corrupts thinking blocks, creates unrecoverable 400
Resolved 💬 3 comments Opened Feb 16, 2026 by dwillitzer Closed Feb 20, 2026
Bug Report
Summary
When running parallel subagents (e.g., Haiku 4.5 agents spawned from an Opus/Sonnet parent session), a race condition in message interleaving causes ensureToolResultPairing to duplicate/reorder assistant messages. This modifies thinking blocks that the API requires to remain immutable, producing an unrecoverable HTTP 400 error that bricks the session.
Environment
- Claude Code version: 2.1.41
- OS: macOS Darwin 25.2.0
- Parent model: claude-opus-4-6 (thinking model)
- Subagent model: claude-haiku-4-5-20251001
- Session ID:
24f525d2-1938-4fd9-b335-10865791ab2b
Reproduction Steps
- Start a session with a thinking model (Opus 4.6 or Sonnet 4.5)
- Spawn multiple parallel subagents (background Task agents using Haiku)
- Have subagents execute concurrent tool calls (Read, Bash, etc.)
- The parent session's message history becomes corrupted
Failure Chain (from debug log)
Step 1: First repair attempt — ensureToolResultPairing detects missing tool_result and attempts repair:
[ERROR] ensureToolResultPairing: repaired missing tool_result blocks (5 -> 5 messages).
Message structure: [0] user; [1] assistant(tool_uses=[3 tools]); [2] user(tool_results=[3 tools]);
[3] assistant(tool_uses=[2 tools]); [4] user(tool_results=[1 tool]) ← missing result for toolu_01MPrYU49mL6TyuBArxWWGf5
Step 2: Second repair makes it worse — duplicate assistant message IDs appear:
[ERROR] ensureToolResultPairing: repaired missing tool_result blocks (11 -> 12 messages).
Message structure shows msg_01GnCpigH1Wc69eHUo9Kresa appearing 3 times,
msg_01BZRXpVRbPJySPqMnHD5289 appearing 2 times
Step 3: Fatal 400 error — modified thinking blocks rejected by API:
API error (attempt 1/11): 400 {"type":"error","error":{"type":"invalid_request_error",
"message":"messages.3.content.1: `thinking` or `redacted_thinking` blocks in the latest
assistant message cannot be modified. These blocks must remain as they were in the original
response."},"request_id":"req_011CYCBf9s9LMdufmDMZXR3b"}
Impact
- Severity: Critical — session is unrecoverable after corruption
- No user warning — the repair function fires silently, corruption escalates without notification
- Data loss — all in-flight subagent work and session context is lost
- Scaling blocker — parallel subagent execution is a core workflow pattern; this makes it unreliable with thinking models
Expected Behavior
ensureToolResultPairingshould never duplicate or reorder assistant messages- If message history is corrupted beyond repair, the session should degrade gracefully (e.g., trigger auto-compact or warn the user) rather than enter an unrecoverable state
- Thinking block immutability should be preserved through any repair/compaction operations
Suggested Mitigations
- Guard thinking blocks: Before any message reordering/repair, snapshot and restore original thinking blocks
- Detect corruption early: If
ensureToolResultPairingfires, validate that thinking blocks are unmodified before sending to API - Graceful degradation: On 400 due to thinking block modification, auto-compact the conversation to reset message history rather than bricking the session
- Concurrency guard: Serialize message history mutations when multiple subagent streams are writing concurrently
Workaround
Users can avoid this by:
- Limiting parallel subagent concurrency when using thinking models as parent
- Using
/resumeto recover from a new session after the crash - Preferring sequential subagent execution for critical workflows
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗