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)
- Lenguaje dominante
- Python
- Estrellas
- 145k
- Forks
- 23.1k
- Métricas de merge de PR
- Métricas de PR pendientes
Descripción
## 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.
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- node.js, python
- Área
- backend, operating-systems
- Tipo de issue
- Error
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Activo
- Claridad
- Necesita aclaración
- Aptitud para principiantes
- 25/100