[BUG] Backend busy-polls at ~266k syscalls/sec with zero file I/O, holding 1.1 cores per process indefinitely (Windows, 2.1.234)

Status Open
Reported on v2.1.234
Maintainer reply None cached
Activity 0 comments · opened Aug 24, 2026

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.

View original on GitHub ↗