[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

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 2, 2026

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 sleeppmset -g log shows 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

  1. Watchdog the case where a request is pending but no socket activity and no timer ever fires — surface an error instead of hanging indefinitely.
  2. Investigate the mimalloc scavenger spin in long-lived processes (possible interaction with a large, long-lived heap).
  3. A symbol-mapped build would let users attach a meaningful profile; today everything below the Bun entry points resolves as ???.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗