[BUG] Bash tool dispatch blocks the event loop ~80s in a CPU spin

Status Open
Reported on v2.1.231
Maintainer reply None cached
Activity 0 comments · opened Aug 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In a --resumed session with a large context (Sonnet 1M window, effectiveWindow=947000), every Bash tool dispatch synchronously blocks the main thread for ~80–86 seconds before the shell is even spawned. The actual command then completes in ~0.3–0.5s. During the block the TUI is completely dead (spinner not animating, stopwatch frozen, input ignored), the API SSE stream goes unserviced, and MCP SSE connections drop. The process always recovers after the stall — this is not a hang.

The block is an active CPU spin, not a wait: the process's own stall instrumentation reports cpu ≈ wall for every stall, and live /proc sampling during a stall shows the main thread in state R at 100% of one core with zero voluntary context switches for the entire duration.

At the time of writing, one session accumulated 44 event-loop stalls, cumulative 3,685,925 ms (~61 minutes) of frozen UI. 36 of the first 40 Bash dispatches stalled; zero of 53 non-Bash dispatches (Read/Edit/Grep/WebFetch) stalled.

What Should Happen?

The stalls should not hold the process

Error Messages/Logs

Steps to Reproduce

  1. Session with a very large context (resumed session on a 1M-window model, several hundred k tokens used)
  2. Any Bash tool call
  3. → entire process freezes ~80s at 100% CPU before the shell spawns; recovers; repeats on every subsequent Bash call, duration slowly growing with context

Non-Bash tools never trigger it. Fresh/small sessions never trigger it.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.231

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Environment

  • Claude Code 2.1.231 (cc_version=2.1.231.d74 / .9fa), native install (~/.local/bin/claude), Bun runtime (threads: mi-scavenger, Bun Pool, JITWorker)
  • Linux 6.8.0-138-generic x86_64 (Ubuntu 24.04), zsh, plenty of free RAM
  • Session: claude --resume --debug, model claude-sonnet-5 with 1M context beta (effectiveWindow=947000), large resumed transcript
  • Stalls occur both on source=repl_main_thread turns and inside a custom subagent (cc_is_subagent=true, source=agent:custom:*) — the subagent's Bash-heavy loop makes it one freeze per command, back to back

Signature in the debug log

Every stall begins exactly at tool_dispatch_start tool=Bash (back-computing stall start = report time − blocked duration; 25/25 stalls traced land on a Bash dispatch). The block sits after the permission decision (1–3 ms) and before Spawning shell:

05:29:45.016Z [INFO] [Stall] tool_dispatch_start tool=Bash toolUseId=toolu_01E3... permissionDecisionMs=2
   ... ~80s of nothing ...
05:31:05.278Z [WARN] [event-loop-stall] blocked for 80227ms (expected 200ms, actual 80427ms).
               Total stalls: 5, cumulative: 424143ms [likely sleep/wake] cpu=80829ms majflt=0 rss=943MB heap=184MB ext=68MB
05:31:05.3xx  [DEBUG] Spawning shell without login (-l flag skipped)
05:31:05.4xx  [INFO] [Stall] tool_dispatch_end tool=Bash ... outcome=ok durationMs=80447   <-- actual exec ≈ 0.3–0.5s of this

Notes on the log evidence:

  • cpu=80829ms for an 80.2s wall block on every stall → active spin. The [likely sleep/wake] label the stall detector attaches is a misclassification (minor secondary bug — the heuristic apparently doesn't check the CPU counter it prints).
  • Stall duration crept from 80.2s → 86.6s as the session grew, suggesting the hot work scales with transcript/context size.
  • The first stall of the resumed session (101s) happened during request assembly, between Dynamic tool loading: found 4 discovered tools in message history and 4/42 deferred tools included — i.e. inside a message-history scan, before any Bash call. All subsequent stalls anchor to Bash dispatch.
  • One 168s stall covered two back-to-back Bash dispatches.
  • Collateral in the log (effects, not causes): Streaming stall detected: ~80s gap between events on nearly every affected turn (SSE events sat in the socket buffer while the loop was blocked); MCP server SSE connection drops every stall (SSE stream disconnected ... Terminal connection error 3/3 → Closing transport, then reconnect); [Stall] stream_idle_partial watchdog warnings.

Live process evidence (sampled during a stall)

Main thread of the running CLI (PID sampled at 2s intervals mid-stall):

state=R ut=1160.3 st=1902.2 wchan=0 voluntary_ctxt_switches=242773 nonvoluntary=263370
state=R ut=1161.1 st=1903.4 wchan=0 voluntary_ctxt_switches=242773 nonvoluntary=263500
state=R ut=1162.5 st=1906.0 wchan=0 voluntary_ctxt_switches=242773 nonvoluntary=263850
state=R ut=1164.1 st=1908.5 wchan=0 voluntary_ctxt_switches=242773 nonvoluntary=264190
  • ~100% of one core: ~0.38 s/s user + ~0.63 s/s system time — roughly two-thirds kernel time
  • voluntary_ctxt_switches completely frozen for the whole stall → the thread never blocks on I/O, locks, or sleeps; it stays runnable in a tight loop of non-blocking, syscall-heavy work (pattern is consistent with heavy allocation/madvise churn — RSS oscillates 535 MB ↔ 990 MB between stalls while JS heap stays at ~135–225 MB)
  • Lifetime totals for the main thread after ~1h40m: 18 min user + 30 min kernel CPU
  • majflt=0 throughout — no swapping/paging

Ruled out

  • Endpoint security (Microsoft Defender for Linux / wdavdaemon): its CPU counters do not move at all during stalls
  • Memory pressure: majflt=0, 62 GB machine, flat behavior
  • Network/VPN: the stall is CPU-bound; API first-byte times are ~1s when the loop is live
  • Hooks: Hooks: Found 0 total hooks in registry
  • Per-command cost: identical ~80s regardless of the command; the same build on the same machine in a small-context session dispatches Bash in milliseconds

Related (but distinct) issues

  • #88257 — recoverable event-loop stall + CPU burn, but first-prompt-only and reproduces in a tiny repo
  • #89062 — 100%-CPU main-thread loop on 2.1.231+, but permanent (never recovers) and makes no syscalls; this one recovers every time and is ~2/3 kernel time

Attachment

Full --debug log of the affected session attached (sanitized: username, employer-internal hostnames, and project name replaced; no other lines altered — all [Stall]/[event-loop-stall] lines verbatim).

debug-log-sanitized.txt

View original on GitHub ↗