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

Resolved 💬 6 comments Opened Apr 21, 2026 by hiroki-kamata 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 ↗

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