anthropics / anthropics/claude-code
[BUG] Windows: ~5s event-loop stall on first Enter after idle — working-set trim causes 41k major page faults (duplicate message submission)
- 主要言語
- Python
- スター
- 145k
- フォーク
- 23.1k
- PR マージ指標
- PR 指標を取得中
説明
## Summary
On Windows, after a session has been idle for a couple of minutes, the first `Enter` does not
register for ~5 seconds. The TUI does not repaint, so it looks like the keystroke was dropped —
users press `Enter` again, and both newlines are delivered when the block clears, submitting the
message twice.
Claude Code's own `[event-loop-stall]` detector catches it and the WARN line contains the cause:
the Node event loop is blocked taking **tens of thousands of major page faults**, because Windows
has trimmed the idle process's working set out to disk.
**The machine is not short of memory when this happens.** In the capture below there was
**12.5 GB of RAM free** and commit was at 62%. Windows trims the working set because the process
is *idle*, not because of memory pressure — so this reproduces on well-provisioned machines and
is not fixed by closing other apps or adding RAM.
## The evidence
Captured with `claude --debug-file `, build 2.1.261.355, Windows 11 (10.0.26200), 32 GB RAM.
Session had been idle 2m25s. User pressed Enter at 19:56:10.8 local.
Last log line before the block:
```
14:26:10.819Z [DEBUG] Hooks: checkForNewResponses returning 0 responses
```
Then, 5 seconds later:
```
14:26:15.864Z [WARN] [event-loop-stall] blocked for 4940ms monotonic (wall drift 4940ms,
clock jump 0ms, expected 200ms). Total stalls: 1, cumulative: 4940ms monotonic / 4940ms wall
blocked_write=false last_write=0ms/2145B sigcont=false
cpu=77ms majflt=41105 rss=664MB heap=151MB ext=63MB
```
Reading the fields:
| field | value | what it rules out |
|---|---|---|
| `majflt=41105` | 41,105 major page faults | this is the mechanism — pages fetched from disk |
| `cpu=77ms` | 77 ms CPU across 4,940 ms (1.6%) | not CPU-bound work, not GC |
| `clock jump 0ms` | no wall/monotonic divergence | machine did not sleep or hibernate |
| `sigcont=false` | not suspended/resumed | not OS process suspension |
| `blocked_write=false` | terminal write path clear | not the renderer, not ConPTY backpressure |
Immediately after unblocking, the UserPromptSubmit hooks run and the TUI issues one full repaint
of everything it owed:
```
14:26:16.046Z [DEBUG] Hook output does not start with {, treating as plain text
14:26:16.092Z [DEBUG] High write ratio: blit=0, write=18684 (100.0% writes), screen=307x156
14:26:16.127Z [DEBUG] [API REQUEST] /v1/messages ... source=repl_main_thread
```
An external sampler confirms it independently, watching the same process across the same seconds
(`cpu% : working_set_MB : private_MB`):
```
19:56:08.9 9.8 : 504 : 834
19:56:11.2 9.0 : 607 : 835 <- Enter; block begins
19:56:13.4 0.0 : 638 : 835 <- 0% CPU, inside the block
19:56:15.6 0.0 : 661 : 835 <- 0% CPU, inside the block
19:56:17.8 12.0 : 714 : 946
19:56:28.9 10.6 : 946 : 858
19:56:31.3 4.8 : 946 : 858 <- settles
```
Working set climbs **504 MB → 946 MB** while private (committed) bytes stay flat at ~834–858 MB.
Committed-but-not-resident is memory that is on disk. That is a fault-in, not an allocation.
## Impact
1. **Enter appears to do nothing for ~5 s.** The prompt *is* accepted and stamped into the
transcript immediately; it is the hooks and the repaint that are late, so there is no visual
feedback at all during the block.
2. **Duplicate message submission.** The natural response — pressing Enter again — is buffered
and delivered on unblock. This is the most damaging symptom, and it reads to the user as a
separate bug.
3. **Apparent render garbling**, from the single large catch-up repaint.
## Reproduction
1. Windows, a session with a large context (~150 MB heap / ~950 MB private bytes here).
2. Leave it idle 2+ minutes while other processes are active.
3. Type a message and press Enter once.
4. `grep "event-loop-stall" | grep WARN`
Reproduces on **2.1.260 through 2.1.266** — this is not a regression from any one build. Six
concurrent sessions on this machine all sat 40–60% non-resident after a few idle minutes.
## Notes toward a fix
I don't know which allocation the loop touches first on prompt submit, but the fault storm implies
something walks a large structure synchronously in that path. Two observations that may help:
- The faults are serviced as ~41k individual page faults. Reading the same process's committed
private regions sequentially from an external process faults **442 MB back in 441 ms** — roughly
**10x faster** than the process does it for itself, which is what you would expect from
read-ahead clustering on sequential access versus scattered single-page faults. So part of the
cost is the access *pattern*, not the volume.
- `[event-loop-stall]` already carries everything needed to diagnose this. Surfacing a hint in the
TUI when a stall exceeds a threshold ("catching up, one moment") would remove the duplicate-submit
symptom even without fixing the underlying fault storm, because the user would stop pressing Enter.
## Workaround, for anyone landing here
Wait ~5 s rather than pressing Enter again. If you want it gone, touching each `claude.exe`'s
committed private memory from an external process on a ~90 s timer keeps the working set resident;
a pass costs ~60 ms when nothing has been trimmed. Note this needs no Windows setting change and
no working-set quota call.
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
No source file or test is named. Reproduce on Windows after two minutes idle using --debug-file, then inspect the prompt-submit path and the existing [event-loop-stall] detector while comparing major page faults and repaint timing. Done should be demonstrated by eliminating the post-idle stall and duplicate submission, or by providing clear catch-up feedback that prevents the second Enter.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- node.js
- 領域
- cli, operating-systems, performance
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 38/100