[Bug] Sessions freeze intermittently without error output

Status Open
Reported on v2.1.261
Maintainer reply None cached
Activity 0 comments · opened Sep 4, 2026

Session hangs indefinitely at 100% CPU in a non-terminating main-thread loop

Summary

Claude Code sessions become permanently unresponsive while pinning one CPU core
at 100%. The process is running, not blocked — it is stuck in a loop on the
main JavaScript thread that never exits. Because that same thread drives the
terminal UI and input handling, the session cannot be redrawn or interrupted:
Ctrl-C, SIGINT and SIGTERM are all ignored, and kill -9 is the only way
out.

This is not a stalled network request. A hung API call leaves the process
asleep at ~0% CPU; here the process is saturating a core with no I/O pending.

I captured two independently-hung sessions and they converge on the same call
path, so this looks like one deterministic bug rather than sporadic slowness.

Environment

| | |
|---|---|
| Claude Code | 2.1.261 (native installer, ~/.local/share/claude/versions/2.1.261) |
| OS | macOS 26.0.1 (build 25A362), Darwin 25.0.0 |
| Hardware | Apple M4, 10 cores, arm64 |
| Terminal | iTerm2, TERM=xterm-256color |
| Shell | zsh |

Frequency: recurring, several times a week. Both captures below are from
2.1.261, but the hang predates it for me — I cannot say when it started, so I
am only claiming direct evidence for 2.1.261.

Non-default settings that could plausibly interact with the render loop:

{
  "tui": "fullscreen",
  "verbose": true,
  "model": "opus[1m]",
  "effortLevel": "high"
}

Enabled plugins: superpowers, mattpocock-skills, i-have-adhd,
swift-lsp, rust-analyzer-lsp. A statusLine command hook is configured.

Observed behaviour

Two hung processes, captured live:

| PID | Invocation | Elapsed | CPU time consumed |
|---|---|---|---|
| 82768 | claude (terminal had since closed; orphaned, no tty) | 19m 41s | 12m 31s |
| 94813 | claude -r on ttys002 | 4m 44s | 3m 26s |

Both in process state R (running) at ~100% CPU. Thread breakdown from
sample: the main thread is the only busy one. Every other thread is
parked —mi-scavenger, all tokio-rt-worker threads and the Bun pool threads
sit in __psynch_cvwait / __ulock_wait.

lsof shows no pending subprocess, no active read, and nothing open beyond the
executable itself (26 and 37 file descriptors respectively).

Each session's last transcript write coincides with the moment it froze
(PID 82768's final write was 17 minutes before capture, matching its elapsed
time). So the hang occurs between turns — during rendering or while preparing
a request — not while streaming a response.

Diagnostic evidence

Captured with macOS sample. Three captures are attached.

1. The loop makes zero progress. Two samples of PID 82768 taken ~30s apart,
after it had already been stuck for 18 minutes, produced a bit-identical
main-thread call stack (identical hash over all frame offsets). This is a
non-terminating loop, not slow work on a large input.

2. Two unrelated processes hang in the same place. The two sessions were
started hours apart in different project directories. They share an
identical 28-frame outer call path and re-converge on the same frame
+0x30d4224 immediately before entering the hot loop. Only 2–3 frames in the
middle differ, i.e. two different call sites reaching the same routine.

3. The innermost loop looks like an unbounded scan. Within the shared path,
frame +0x3564f90 repeats exactly 6 times — a recursive descent. The
leaf then cycles between three addresses a few bytes apart, with sample counts
split roughly evenly between them (833 / 826 / 823 of 2556 samples). That
shape is consistent with a tight scan over a string or array whose termination
condition is never met.

4. CPU time grows linearly with wall time, confirming a genuine spin rather
than a deadlock:

PID 82768:  10:19 CPU @ 17:28 elapsed  →  12:31 CPU @ 19:41 elapsed
PID 94813:   1:13 CPU @  2:31 elapsed  →   3:26 CPU @  4:44 elapsed

Main-thread frames (offsets from load address, outermost first)

Shared by both processes unless marked. Offsets are stable across the two
captures of PID 82768.

+0x112efd4
+0x104eb84
+0x1053528
+0xdfa304
+0x13a0f94
+0x112c468
+0x13a0bc8
+0x6ed9d4
+0x1a73c8
+0x2bde880
+0x28affe4
+0x35418b4
[JIT frame]
[JIT frame]
+0x588f44
+0x31d337c
+0x3074cbc
+0x2dd5068
+0x2dd9b58
+0x35418b4
+0x3564f90      <-- repeats 6x (recursive descent)
+0x3564f90
+0x3564f90
+0x3564f90
+0x3564f90
+0x3564f90
[JIT frame]
[JIT frame]
   ...          <-- 2-3 frames differ between the two processes here
+0x30d4224      <-- both re-converge on this frame
[JIT frames, tight loop, never returns]

+0x35418b4 appearing twice, and the 6x repeat of +0x3564f90, are
consistent with a call trampoline plus a recursive tree/text walk.

Frames cannot be symbolized locally: the shipped binary is a ~199 MB
stripped Bun single-file executable, so sample resolves almost everything to
???. Only dyld, libsystem_* and a few Rust/Bun-internal symbols
(rust_eh_personality, _pthread_start) resolve. Someone with a symbolized
build or the source map should be able to resolve these offsets directly.

Trigger and reproduction

The hang has a consistent trigger: asking for a large file to be generated in
an interactive TUI session.
In my case it is a documentation/lesson HTML file
of ~11 KB / ~270 lines.

Both hung sessions whose transcripts I inspected end the same way — the user
prompt and its attachments are committed to the transcript, and then nothing.
Not one assistant content block is ever written:

| Session | Last prompt | Assistant output after it |
|---|---|---|
| cf4d4a74 (PID 8060) | "Generate the first lesson" | none |
| ff6141cd (PID 94813) | "continue" | none |

Reading the full transcript of cf4d4a74, the session had completed six
Bash tool calls reading project files, and the next step would have been a
single large Write of the HTML document. It hung there — while streaming that
large tool-use block, before it could be committed.

The discriminating test

Same repo, same prompt, same model, tools pre-authorised. Only the interface
differs:

# Interactive TUI: hangs at 100% CPU, never completes.
claude          # then: "Generate the first lesson"

# --print, no TUI: completes in ~2 minutes, writes the file, 0.0% CPU throughout.
echo "Generate the first lesson" | claude -p --allowedTools "Bash,Read,Write,Edit,Glob,Grep"

Print mode produced a correct 266-line HTML file and exited cleanly. The work
itself, the model output and the tool execution are all fine. Only the
interactive render path hangs.

Recipe

  1. A project where the natural response is writing one large file (mine: a repo

with a MISSION.md, some CSS/JS assets, and an empty lessons/ directory,
so "generate the first lesson" yields a single ~11 KB HTML Write).

  1. Start an interactive session — tui: "fullscreen" in my config.
  2. Submit the prompt. Roughly one attempt in a handful hangs; over an afternoon

it hit four times.

  1. Capture before killing: sample <pid> 3 -f /tmp/hang.sample, and confirm the

stack reaches +0x30d4224.

View original on GitHub ↗