Esc during background task completion causes duplicate API requests and unrecoverable 400 error loop

Resolved 💬 4 comments Opened Feb 20, 2026 by scottleibrand Closed Mar 21, 2026

Bug: Esc during tool execution causes duplicate API requests with corrupted message history

Environment

  • Claude Code version: 2.1.45
  • OS: macOS (Darwin 25.2.0)
  • Model: claude-opus-4-6

Description

Hitting Esc while Claude is processing a tool result sometimes causes two parallel API requests to fire simultaneously from the same REPL session. One of the requests inherits an orphaned tool_result block from the aborted request, causing a 400 error that puts the session into an unrecoverable error loop.

Reproduction steps

  1. In a long-running session, run several background Bash commands (e.g., polling scripts with run_in_background: true)
  2. Hit Esc to interrupt Claude while it's working
  3. Background task completion notifications start flooding in — each one triggers Claude to resume processing
  4. Hit Esc repeatedly to try to get Claude to actually stop
  5. The rapid Esc + background notification cycle creates a race: two API requests fire simultaneously
  6. One request contains a tool_result block referencing a tool_use_id that no longer exists in the conversation (it was part of the aborted request)
  7. The API returns 400: unexpected tool_use_id found in tool_result blocks
  8. The orphaned tool_result persists in the conversation history, so every subsequent API call fails with the same error — the session is unrecoverable

The core issue is that background task completions keep re-triggering Claude after each Esc, making it very hard to actually stop the session. Each Esc+resume cycle increases the chance of the race condition.

Observed behavior

The REPL shows two responses being generated in parallel (both visible on screen). Only one responds to Esc. The session then enters a loop of 400 errors:

API Error: 400
{"type":"error","error":{"type":"invalid_request_error",
"message":"messages.0.content.1: unexpected `tool_use_id` found in `tool_result` blocks:
toolu_vrtx_01Wg5neec5vtEgonUBXhCkxS. Each `tool_result` block must have a corresponding
`tool_use` block in the previous message."}}

Evidence from debug log

The debug log shows the race condition clearly (timestamps in milliseconds):

T+0ms    [ERROR] Error in API request: Request was aborted.
T+1ms    [DEBUG] Messages deferred by 1 (628→629)
T+43ms   Started caffeinate to prevent sleep
         ... new API turn begins ...
T+106ms  [DEBUG] attribution header cc_version=2.1.45.cdb    ← request A (background/subagent?)
T+110ms  [DEBUG] attribution header cc_version=2.1.45.242    ← request B (main process?)
T+500ms  [ERROR] API error: output_config: Extra inputs are not permitted   ← request A fails
T+560ms  [ERROR] API error: unexpected tool_use_id in tool_result blocks    ← request B fails

Two API requests with different cc_version suffixes (.cdb vs .242) fire within 4ms of each other from the same process, suggesting two code paths racing after the abort.

Expected behavior

  • Esc should cleanly abort the current request and clean up any pending tool_result blocks from the conversation history
  • Only one API request should be active at a time in a single REPL session
  • If an orphaned tool_result is detected, the session should self-heal (strip the orphaned block) rather than entering an unrecoverable error loop

Additional context

  • The session was using hooks (Stop hook, PostToolUse hook) which may interact with the abort timing
  • A background task completing around the same time as the Esc may contribute to the race
  • The two different cc_version suffixes suggest the main process and a subagent/background task are both trying to send API requests after the abort

-Claude

View original on GitHub ↗

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