Claude Code 2.1.116 freezes with main thread in V8/JSC tight loop (100% CPU, all Anthropic API connections lost, no recovery)

Status Fixed / completed
Reported on v2.1.116
Maintainer reply None cached
Activity 6 comments · opened Apr 21, 2026 · closed Apr 29, 2026

Summary

Claude Code 2.1.116 on macOS 26.4.1 (arm64) hangs indefinitely with the main thread spinning in a tight JavaScript runtime loop (100% CPU on main thread, no I/O wait). The process remains alive but unresponsive; /exit and keyboard input are ignored. New Claude Code sessions spawned from the same project exhibit the same freeze repeatedly (observed 3 times in the same day).

Processes still hold localhost connections (OTLP exporter, SigNoz, obsidian-cli MCP), but lose all Anthropic API HTTPS connections. They cannot recover.

Environment

  • Claude Code version: 2.1.116
  • OS: macOS 26.4.1 (25E253), arm64 (Apple Silicon)
  • Terminal: Ghostty (TERM=xterm-ghostty) via Zellij multiplexer
  • Plugins loaded at time of freeze: spec-workflow, persistent-logging, signoz-integration, chrome-devtools-mcp, maestro-mcp, basic-developer, marp-presentation, local-mcp, obsidian-rag, stitch, and others (111 plugin skills loaded per debug log)
  • MCP servers connected: signoz, chrome-devtools-mcp, maestro-mcp, spec-workflow (@pimzino/spec-workflow-mcp 2.2.5), harness-mcp (Go binary), persistent-logging, ai-agent-mcp, obsidian-rag
  • OTel exporter active (OTLP gRPC → localhost:4317 → SigNoz in Docker)
  • Shell hook chain: custom harbest hook wrapping tool calls, rtk (Rust-based) hook rewriting Bash commands

Reproduction

Observed 3 times during spec-workflow QA phase execution on a single feature branch within the same day. Common conditions:

  1. Long-running session with many MCP tool invocations (especially spec-workflow 36-tool family and signoz_* aggregation)
  2. Large tool results being rendered (implementation logs, multi-KB markdown documents, .test-logs/ reads via rtk cat)
  3. Background sub-agent spawning via Task tool with run_in_background: true
  4. /exit from a frozen session sometimes fails to terminate the process

A reliable trigger has not been isolated. Each freeze occurred within ~30–90 min of session start during heavy tool-use periods.

Evidence — process sampling

Captured with sudo /usr/bin/sample <PID> 2 -file stack-<PID>.txt while 3 frozen processes were still running. All 3 instances showed the same signature: main thread at 100% with an identical deep call chain through V8/JSC internals across every one of ~1300 samples in a 2-second window.

PID 32051 — sample header

Process:         2.1.116 [32051]
Code Type:       ARM64
Platform:        macOS
Target Type:     live task
Date/Time:       2026-04-21 21:27:54.995 +0900
Launch Time:     2026-04-21 20:00:25.227 +0900
OS Version:      macOS 26.4.1 (25E253)
Physical footprint:         553.7M

The process had been alive and unresponsive for ~87 minutes at sample time.

Main thread stack (all 3 processes — identical shape)

1291 Thread_xxx  DispatchQueue_1: com.apple.main-thread  (serial)
+ 1289 start  (in dyld) + 6992
  + 1289 ???  (in 2.1.116) + 0x85f944
    + 1289 ???  (in 2.1.116) + 0x8639b8
      + 1289 ???  (in 2.1.116) + 0x2e7518
        + 1289 ???  (in 2.1.116) + 0xb3484c
          + 1289 ???  (in 2.1.116) + 0xba2834
            + 1289 ???  (in 2.1.116) + 0xe01104
              + 1289 ???  (in 2.1.116) + 0xba2620
                + 1289 ???  (in 2.1.116) + 0x6fe524
                  + 1289 ???  (in 2.1.116) + 0x468700
                    + 1289 ???  (in 2.1.116) + 0x2ac2bd8
                      + 1289 ???  (in 2.1.116) + 0x2853078
                        + 1289 ???  (in 2.1.116) + 0x34182b8
                          + 1289 ???  (in <unknown binary>) [0x12193b8b8]
                            + 1289 ???  (in <unknown binary>) [0x12193b888]
                              + 1289 ???  (in 2.1.116) + 0x3b57dc
                                + 1289 ???  (in 2.1.116) + 0x2e98c10
                                  + 1289 ???  (in 2.1.116) + 0x2db78bc
                                    + 1267 ???  (in 2.1.116) + 0x2db7a0c
                                      : 1236 ???  (in 2.1.116) + 0x2c39508
                                        | 1077 ???  (in 2.1.116) + 0x2c43548
                                          + 1049 ???  (in 2.1.116) + 0x2c439b4
                                            ! 1019 ???  (in 2.1.116) + 0x34182b8
                                              : 460 ???  (in <unknown binary>) [0x1219c0dd4]

The <unknown binary> frames in high memory (0x1219xxxxx) are JIT-compiled code — classic V8/JSC signature. The deep recursion through offsets 0x2c39508, 0x2c43548, 0x2c439b4, 0x34182b8 repeating identically across all samples indicates either an infinite loop in interpreted/JIT JavaScript or a pathologically slow synchronous operation (catastrophic-regex backtracking, O(n²) synchronous parsing, or deep recursion on cyclic input).

Symbols in the 2.1.116 binary are stripped in the shipped build; only offsets from load base are available.

Child runtime threads differ per instance

Each frozen process loaded a different set of child runtimes, suggesting the loop is reached via different tool paths (but converges to the same hot loop):

| PID | Distinctive threads | Likely trigger |
|-----|--------------------|----------------|
| 32051 | JavaScriptCore libpas scavenger, Heap Helper Thread, CFThreadLoop | JSC-based MCP child |
| 43590 | Bun Pool 7Bun Pool 11 | Bun-based MCP child (harness-mcp spawns bunx-based subprocesses) |
| 51477 | tokio-rt-worker ×6 | Rust subprocess (likely rtk hook wrapper) |

Evidence — network & FD state

Compared via lsof -p <PID> on 3 frozen PIDs vs 1 live PID:

  • Frozen processes: ~24–30 unix domain sockets (MCP stdio pipes), ~5 localhost ESTABLISHED connections to :8003/:4317/:58080 (OTel / SigNoz / obsidian-cli). Zero Anthropic API HTTPS connections.
  • Live process: Same localhost connections plus 4 HTTPS connections to Anthropic API (35.190.46.17:https, [2607:6bc0::10]:https, 34.149.66.137:https).

The frozen processes have lost their API transport entirely and never reconnected. This is consistent with the main thread being blocked from running the reconnect logic.

Debug log state

~/.claude/debug/ contains logs but the latest symlink points to a session from 2026-04-08 — no fresh debug log for the frozen sessions because debug mode was not enabled. Without symbols and without debug-mode traces, there is no path from the offsets to source functions from the user side.

Why this matters

  • Data loss risk: session transcripts can become stale because the frozen process cannot flush final state before being SIGKILLed.
  • Resource leak: each frozen process occupies ~550 MB RSS. After 3 freezes, ~1.6 GB is held by zombies.
  • MCP subprocess leak: frozen parents leave child MCP processes (harness-mcp, spec-workflow-mcp, signoz-mcp, etc.) orphaned or stuck waiting on unix sockets.
  • Recovery cost: manual kill, loss of in-flight context, and session restart.

Requested

  1. Symbolication / source mapping of offsets 0x2c39508, 0x2c43548, 0x34182b8, 0x2db78bc in the 2.1.116 ARM64 macOS binary — these are the fixed-point of the loop across 3 independent freezes.
  2. Main-thread watchdog: detect main-thread stall >30 s and emit diagnostic state (active tool call id, last API request id, pending async operations) to ~/.claude/debug/ before the session becomes fully unresponsive.
  3. Anthropic API transport auto-reconnect: currently, once all HTTPS connections to the API drop, the process enters a wedged state with no recovery path.
  4. Large-payload rendering audit: review whether tool-result rendering (multi-KB markdown, JSONL transcripts, implementation logs) uses a synchronous pathway that can exhibit O(n²) or catastrophic-regex behaviour on pathological input.

Attachments

Full sample(1) outputs for the 3 frozen PIDs (191–212 KB each) are available on request. Symbols are stripped but offsets and thread topologies are preserved.

---

Reported from a day where 3 Claude Code sessions froze in sequence while driving a spec-workflow feature branch through its full phase lifecycle (requirements → qa → audit). The live session that captured these samples survived only because we switched to background-agent delegation after the 2nd freeze.

View original on GitHub ↗

6 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/18532
  2. https://github.com/anthropics/claude-code/issues/36729
  3. https://github.com/anthropics/claude-code/issues/51193

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

0xbrainkid · 4 months ago

This is a severe liveness failure because the process does not simply crash and restart, it remains alive while becoming operationally dead: 100% CPU on the main thread, no useful input handling, API connectivity gone, and no recovery path from inside the session.

That combination is especially damaging because it creates ambiguity for operators. A dead process is obvious. A process that still exists, still holds local connections, but silently stops making forward progress is much harder to reason about and can waste a lot of debugging time.

The repeated pattern across multiple sessions in the same project also suggests this is not just a one-off machine hiccup. Even without a minimal trigger, the report already establishes a meaningful runtime-failure class: the host surface can enter a non-recovering busy-loop state under heavy tool/plugin load.

Damok124 · 4 months ago

Confirming independently on a different setup — not plugin-interaction specific.

Environment

  • Claude Code 2.1.116, macOS Apple Silicon
  • Single custom stdio MCP server — heavy tool-use session with ~30 sequential calls (ops / check / unlock)
  • No custom hooks, no additional plugins beyond the one MCP
  • Model: Opus 4.7 (1M context) with xhigh effort

Trigger
After ~30 tool calls, the assistant entered a final "check + synthesis" step. At freeze time the UI showed the task still running at 7m 49s with ~28.8k tokens downloaded — i.e. the model was actively streaming a very long final response.

Symptoms (same as OP)

  • Main thread pinned at 100% CPU continuously (still 100% 30+ min later)
  • Closing the terminal window did not terminate the process — it survived as an orphan until manual SIGKILL
  • No output reached the scrollback after the freeze started

Notable variant: simpler environment than OP (1 MCP server, no hooks, no third-party plugins), which rules out plugin/hook interactions as a necessary condition. The O(N²) rendering hypothesis on large streamed markdown fits: xhigh effort + Opus 4.7 produces notably larger final outputs, pushing the renderer past a threshold where per-token rendering cost compounds. Prior sessions with the same MCP on Opus 4.6 at default effort (smaller streams) did not trigger this.

myking-saber · 4 months ago

Same symptom on Linux. Adding a data point because this is a clear regression — same workflow didn't have this problem a couple of months ago.

Environment

  • Linux 6.8.0-106-generic, x86_64
  • Session runtime: 3h 14min, Android/MTK kernel codebase (heavy tool output: dmesg/logcat/source reads)

Process state when hung

State:  R (running)
VmPeak: 135513252 kB   (~129 GB)
VmSize: 88690636 kB    (~84 GB)
VmRSS:  4132584 kB     (~4 GB resident)
Threads: 12
CPU: ~27% sustained

Proof that the event loop is dead, not just busy — 3-second sample of /proc/<pid>/status:

voluntary_ctxt_switches:     2445069 → 2445069   (delta: 0)
nonvoluntary_ctxt_switches:  15985   → 16010    (delta: +25)

Zero voluntary context switches means the process never yields for I/O — consistent with V8 trapped in a full-GC loop on a near-max heap, not doing actual work. SIGTERM ignored, SIGKILL required.

Regression window: works fine on older versions, started hanging on long sessions recently — matches the timeline in #33380 / #27421 pointing at post-Sonnet-4.6 ArrayBuffer accumulation. Heavy tool outputs (large dmesg / file reads) appear to accelerate it significantly, consistent with HTTP-response-body retention.

Happy to capture a heap snapshot next time if useful — any preferred flag / NODE_OPTIONS to enable it before the process becomes unresponsive?

thunderrabbit · 4 months ago

This seems to have happened to me on Linux (Pop!_OS). Claude Code wrote this:

Claude Code 2.1.123 — main thread spins after parallel MCP tool returns

Environment

  • claude 2.1.123, Bun-compiled SEA at ~/.local/share/claude/versions/2.1.123
  • Linux 6.17.9-76061709-generic
  • --mcp-config with stdio MCP servers (jikan, openbrain, playwright)
  • No --resume; fresh session

Symptom
Session became unresponsive ~13 min after start. Process kept running at ~94% CPU on a single core for 2h+ without writing any further events to its
~/.claude/projects/<dir>/<session>.jsonl. Ctrl-C, Ctrl-D, repeated, ineffective. SIGKILL was the only way out.

Last entries on the transcript before freeze

  1. mg_api (jikan MCP) tool result returned
  2. mg_api tool result returned — (parallel call)
  3. System task_reminder attachment appended
  4. Silence. Process keeps burning CPU.

Evidence collected from /proc while still hung

  • 18 threads total. Only main thread claude was R (running); all 17 others (Bun pool, V8 HeapHelper ×7, HTTP Client, File Watcher) were S (sleeping). → spin localized to the main JS thread

/ event loop.

  • /proc/PID/io: 15,277,036 read syscalls over 2h 28m wallclock (~1,700/sec). rchar=690 MB, read_bytes=139 KB. syscw=12,245 — basically no writes. → tight non-blocking read loop on a

non-disk fd.

  • 9 socket fds open, but ss -tnp shows zero established TCP connections for the PID. → either Unix-domain (MCP stdio) or already-closed-but-unreaped sockets.
  • HTTP client thread sleeping → not stuck on Anthropic API SSE.
  • Signal handlers (SIGINT/SIGQUIT) effectively wedged: kernel delivers, but the Bun JS callback runs on the next event-loop tick and the main thread never yields.

Hypothesis
After two parallel mg_api (jikan stdio MCP) tool calls returned, the agent loop entered a busy-poll on a child stdio fd that was at EOF / EAGAIN, missing a readable (or analogous Bun
stream) event registration. The task_reminder attachment being inserted at the same moment as the 2nd parallel result may be relevant — possibly a race in the inbound-message merge path
when an attachment lands between two near-simultaneous tool results.

Repro hints (best guess, not verified)

  • stdio MCP server with multiple parallel tool calls in flight that complete within microseconds of each other
  • a system task_reminder injection arriving at the same tick

Recovery
kill -9 of the main PID. Transcript on disk is complete up to the freeze point — no data loss.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.