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

  1. Start a session with a thinking model (Opus 4.6 or Sonnet 4.5)
  2. Spawn multiple parallel subagents (background Task agents using Haiku)
  3. Have subagents execute concurrent tool calls (Read, Bash, etc.)
  4. The parent session's message history becomes corrupted

Failure Chain (from debug log)

Step 1: First repair attemptensureToolResultPairing 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

  • ensureToolResultPairing should 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

  1. Guard thinking blocks: Before any message reordering/repair, snapshot and restore original thinking blocks
  2. Detect corruption early: If ensureToolResultPairing fires, validate that thinking blocks are unmodified before sending to API
  3. Graceful degradation: On 400 due to thinking block modification, auto-compact the conversation to reset message history rather than bricking the session
  4. 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 /resume to recover from a new session after the crash
  • Preferring sequential subagent execution for critical workflows

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗