anthropics / anthropics/claude-code

[Bug] Sessions freeze intermittently without error output

未关闭
#92,222 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area:tui bug platform:macos
主要语言
Python
星标
145k
派生
23.1k
PR 合并指标
PR 指标待抓取

描述

# 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`.

贡献指南

这个仓库没有索引到贡献指南

调研方向

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.

由索引模型根据 Issue 内容生成。

评估

技术栈
bun, javascript, macos
领域
cli, performance
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
50/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。