anthropics / anthropics/claude-code
[BUG] Backend busy-polls at ~266k syscalls/sec with zero file I/O, holding 1.1 cores per process indefinitely (Windows, 2.1.234)
- Ngôn ngữ chính
- Python
- Star
- 145k
- Fork
- 23.1k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
## Summary
Claude Code backend processes enter a permanent busy-poll and hold ~1.1 CPU cores each, indefinitely, on Windows. This is measured rather than inferred: the spinning processes perform **zero file I/O** while issuing **~266,000 non-file I/O operations per second each**.
Related but closed without a fix: #11473 (closed "not planned"), #22275 (closed as duplicate), #19393, #30807, #17148. Filing fresh because those are stale, none has Windows syscall-level numbers, and none includes a control group.
## Environment
| | |
|---|---|
| Claude Code | 2.1.234 |
| OS | Windows 11 Home 26100 |
| CPU | AMD Ryzen 7 7800X3D, 8C/16T |
| RAM | 94.9 GB |
| Shape | One app instance (1 window) hosting ~40-56 `claude.exe` backends |
## Measurement
Four spinning backends, sampled over 5 seconds each via `Win32_Process` counters:
```
pid 18500 cpu+ 6.2s fileIO+0 otherIO+1,359,847 threads 35 handles 361
pid 14796 cpu+ 5.7s fileIO+0 otherIO+1,333,536 threads 35 handles 349
pid 10656 cpu+5.62s fileIO+0 otherIO+1,304,113 threads 36 handles 390
pid 22876 cpu+5.45s fileIO+0 otherIO+1,275,591 threads 35 handles 422
```
`ReadOperationCount + WriteOperationCount` delta is **exactly zero**. `OtherOperationCount` delta is ~1.3M in 5s, i.e. **~266,000 syscalls per second per process**. CPU delta of 5.7s over 5s wall = ~114% of a core.
Nothing is being read or written. That is a wait syscall returning immediately instead of blocking, which matches the `recvfrom() -> EAGAIN -> retry` trace in #11473 exactly, on a different OS.
Thread and handle counts are stable across samples, so this is not a leak.
## What it costs
With 13 backends spinning, the whole machine sat at a flat 100%:
```
8 one-second samples: 100, 100, 100, 100, 100, 100, 100, 100
min 100 max 100 avg 100
```
Two unrelated 9-minute build jobs then overran a 10-minute ceiling. `\Processor Information(_Total)\% Processor Utility` read 109.5%, so the CPU was turboing above base: this is not thermal throttling.
Worth noting for anyone triaging: **Task Manager under-reports this badly** (61% against a true 100%), because at saturation it cannot get scheduled often enough to sample itself. Performance-counter readers are accurate; Task Manager is not. That may be why some reports of this get dismissed.
## Ruled out
- **Not orphaned processes.** All four spinners had live parents, all sharing one app instance.
- **Not user hooks or file watchers.** This project runs heavy `SessionStart`/`PreToolUse` hooks; a hook scanning the tree would move `fileIO`, and `fileIO` is flat zero throughout.
- **Not thermal throttling.** See the 109.5% utility figure above.
- **Not a handle or thread leak.** Both counts stable.
## A correlation worth investigating, and a negative result
Every spinning backend had exactly one notable child:
```
cmd.exe /d /s /c "npx -y @modelcontextprotocol/server-pdf --stdio"
```
That is the bundled **pdf-viewer** plugin's MCP server, communicating over a stdio pipe. A stdio pipe poll is consistent with the syscall signature.
Control group, sampled over 5s across all `claude.exe`:
| | count | avg | notes |
|---|---|---|---|
| **With** the `server-pdf` child | 34 | 25% of a core | 8 of them at ~100% |
| **Without** it | 22 | 2% of a core | max 31%, none spinning |
So the pdf MCP child appears **necessary but not sufficient**: nothing without it spins, but only 8 of 34 with it do. During this session the pdf MCP server was observed disconnecting and reconnecting, which may be the trigger.
**However, killing it does not stop an already-spinning process:**
```
BEFORE: pid 18500 at 97% of a core
killed the server-pdf child and its 2 grandchildren
AFTER: pid 18500 at 86% of a core (control pid 22876, child still alive: also 86%)
```
So once started the loop is self-sustaining and survives removal of the suspected trigger. I am deliberately **not** claiming pdf-viewer as the cause. Identifying the actual polled handle needs an ETW trace.
## A second, separate problem
The same bundled plugin had spawned **100 concurrent `server-pdf` processes** holding **6.77 GB** (avg 69 MB each), on a machine where nothing was using PDF tooling:
```
server-pdf processes: 100
RAM held: 6.77 GB
total claude.exe: 56
```
There appears to be no supported way to disable it: it is not in `installed_plugins.json`, not in `enabledPlugins`, and `claude plugin list` does not show it. A bundled plugin that spawns one `npx` process per session with no opt-out is worth a look independently of the CPU bug.
## What would close this out
An ETW/WPR trace on a spinning process would name the exact call. I did not have Windows Performance Recorder available. If a maintainer can say which handle the event loop is expected to block on, that is probably enough to spot the regression by inspection.
## Reproduction
Not reliably reproducible on demand. It appears on a long-lived box running many concurrent sessions under one app instance, and once a backend starts spinning it never recovers: only ending that session frees the core.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
Start with an ETW/WPR trace of a spinning Windows backend to identify the exact call and handle being polled. Compare the trace with the server-pdf stdio connection and the observed disconnect/reconnect behavior. Done means identifying the regression and stopping the persistent busy-poll without relying on killing the session.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- node.js, python
- Lĩnh vực
- backend, operating-systems
- Loại issue
- Lỗi
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Cần làm rõ
- Mức phù hợp với người mới
- 25/100