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)

オープン
#89,255 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:mcp area:plugins bug perf:cpu platform:windows
主要言語
Python
スター
145k
フォーク
23.1k
PR マージ指標
PR 指標を取得中

説明

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

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
node.js, python
領域
backend, operating-systems
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
説明が足りない
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。