[BUG] Long-lived session wedges: mi-scavenger + HTTP Client threads spin at ~200% CPU while JS main thread is idle; no request is ever dispatched
Preflight Checklist
- [x] I have searched existing issues — closest is #75630 (idle VSCode-launched CLI children spinning at 100 % CPU). This report is from a different launch mode (interactive TUI), a much newer build, includes thread-level attribution, and adds a functional consequence that #75630 does not describe (the session silently stops dispatching requests). Possibly the same root cause; cross-referencing rather than duplicating.
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
Environment
- Claude Code 2.1.220, native install (
~/.local/share/claude/versions/2.1.220, Mach-O arm64) - macOS 15.7.4 (24G517), Apple Silicon
- Started as
claude -r <session-id>(interactive TUI), cwd inside a git worktree - No
ANTHROPIC_BASE_URL/ proxy env set (first-party API); egress via an on-host transparent TUN proxy
What's Wrong?
A session process that had been alive ~42 hours entered a state where two Bun runtime threads burn ~2 CPU cores indefinitely while the JS main thread sits idle in kevent64. The TUI still accepts input — the user's message is written to the transcript — but no API request is ever put on the wire, and no timeout or error ever fires. The session stayed dead for 47 minutes until the process was killed; the resumed process was immediately healthy.
The distinguishing detail versus #75630: this is not merely wasted CPU. The session is functionally wedged — input is accepted and persisted, nothing is dispatched, and none of the client-side timeouts rescue it.
Timeline (relative to process start)
| Elapsed | Event |
|---|---|
| T+0 | process started (claude -r …) |
| T+26h58m | last turn completes normally (assistant text + turn_duration) |
| T+27h01m | away_summary written — last record before the session went idle |
| (~14 h idle) | process keeps burning ~1.7 cores while idle |
| T+41h38m | user types commit (6 chars). Record is written to the transcript. |
| T+41h38m → T+42h25m | 47 min, no assistant record, no API error, no retry |
| T+42h25m | kill + claude -r <same session> → healthy within seconds |
Evidence
1. CPU — affected process vs. its peers. Five concurrently running sessions on the same machine, same version:
| PID | uptime | cumulative CPU | %CPU when observed |
|---|---|---|---|
| affected | 1d 18h | 1545 min | 116–200 % |
| peer 1 | 6d 22h | 53 min | 0.0 % |
| peer 2 | 4d 20h | 35 min | 0.0 % |
| peer 3 | 2d 17h | 26 min | 0.0 % |
| peer 4 | 2d 17h | 14 min | 0.0 % |
1545 CPU-minutes over ~42 h wall clock ≈ 1.7 cores sustained. ps -M showed two hot threads: one with 764 min user time, one with 515 min system time + 249 min user time (syscall-bound — consistent with continuous madvise/mmap).
2. sample(1), 4 s @ 1 ms — 3472 samples per thread.
| Thread | non-idle samples | verdict |
|---|---|---|
| mi-scavenger | 3472 / 3472 (100 %) | spinning |
| HTTP Client | 961 / 3472 | partially spinning |
| com.apple.main-thread | 42 / 3472 (3430 in kevent64) | idle, waiting for events |
| Bun Pool 0–15 | 0 | blocked |
| tokio-rt-worker ×16 | 0 | blocked |
| Heap Helper Thread ×3 | ~6 | blocked |
So the runaway is not application JS and not GC-helper work — it is mimalloc's background scavenger plus Bun's HTTP client event-loop thread.
<details>
<summary>Hot stack heads (binary is stripped, load address 0x10014c000)</summary>
mi-scavenger — 3472/3472 non-idle
thread_start → _pthread_start
→ ??? +0x851904
→ ??? +0x85b2b8 (3469 samples)
HTTP Client — 961/3472 non-idle
thread_start → _pthread_start
→ ??? +0x1e352bc → ??? +0x1501620 → ??? +0x1500f74 → ??? +0x1504000
→ ??? +0x7f9294 (3467 samples)
com.apple.main-thread — idle
start → ??? +0xa28354 → ??? +0x10ab0c8 → ??? +0x10b1220 → ??? +0x561df8
→ ??? +0x112c128 → ??? +0xea5d98
→ kevent64 (3430 of 3472 samples)
Full 40-thread sample output (timestamps redacted) available on request.
</details>
3. Memory is not the trigger. vmmap -summary: physical footprint 505.7 MB (peak 599.2 MB), writable regions 8.8 GB reserved / 537 MB written. Nothing near a heap limit.
4. Ruled out.
- Network — the on-host proxy's connection table showed no long-lived in-flight request for this PID; only a 3-minute-old 22 KB/4 KB request that had already completed. The pending request was apparently never sent.
- MCP servers — all three child MCP processes at 0.0 % CPU.
- User config — no timeout/env tuning was in effect for this process.
- Machine sleep —
pmset -g logshows no Sleep/Wake events in the window.
5. Session scale (possible trigger). Transcript 54 MB / 19,286 records; last request context ≈ 464k tokens; uptime 42 h; resumed several times; cwd inside a git worktree. Other sessions on the same machine have far smaller transcripts and never showed this.
What Should Happen?
An idle session should sit near 0 % CPU, and a session that has accepted user input must either dispatch a request or surface an error — never sit silently forever.
Suggestions
- Watchdog the case where a request is pending but no socket activity and no timer ever fires — surface an error instead of hanging indefinitely.
- Investigate the mimalloc scavenger spin in long-lived processes (possible interaction with a large, long-lived heap).
- A symbol-mapped build would let users attach a meaningful profile; today everything below the Bun entry points resolves as
???.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗