anthropics / anthropics/claude-code
[Bug] Sessions freeze intermittently without error output
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
# 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:
```json
{
"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:
```sh
# 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`).
2. Start an interactive session — `tui: "fullscreen"` in my config.
3. Submit the prompt. Roughly one attempt in a handful hangs; over an afternoon
it hit four times.
4. Capture before killing: `sample 3 -f /tmp/hang.sample`, and confirm the
stack reaches `+0x30d4224`.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
First reproduce the interactive TUI hang with the large-file prompt, then compare it with the completing `claude -p` command. Capture the process using `sample` and trace the TUI render path around `+0x30d4224`, using a symbolized build or source map to resolve the stripped frames. Done means the interactive session completes the large Write without a main-thread spin and remains interruptible.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- bun, javascript, macos
- Domaine
- cli, performance
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 50/100