Chokidar file watchers with unlimited depth exhaust Windows Non-Paged Pool (NPP) kernel memory
Summary
Claude Code creates 8 chokidar file watchers, 4 with unlimited directory depth. On Windows, each watched directory creates a kernel ReadDirectoryChangesW handle consuming Non-Paged Pool (NPP) memory. For projects with deep directory trees (e.g., node_modules/), this can allocate tens of thousands of kernel handles, exhausting NPP.
Analysis
The .bun section of claude.exe v2.1.109 contains 8 Aa.watch() (chokidar) call sites:
| Watcher | depth | Purpose |
|---|---|---|
| Settings files watcher | 0 | Config file changes |
| FileChanged (project files) | unlimited | Source code monitoring |
| Keybindings watcher | unlimited | Keybinding file changes |
| Skill/command directories | 2 | Plugin directory changes |
| (4 duplicates in bundle) | | (same patterns repeated) |
The 4 unlimited-depth watchers are the concern. On Windows, chokidar falls back to ReadDirectoryChangesW which creates one kernel handle per watched subdirectory. A typical Node.js project:
node_modules/: 5,000-20,000 subdirectories.git/objects/: hundreds of pack directoriesdist/,build/,target/: build output trees
With 4 unlimited-depth watchers, a single project could create 20,000-80,000 kernel directory handles. Each handle consumes kernel Non-Paged Pool memory (~1-4 KB per handle for the NTFS FCB + associated structures).
Estimated NPP per project: 20,000 handles × 2 KB avg = ~40 MB NPP per project per claude.exe process.
With 3 concurrent claude.exe processes (main + subagents), each watching the same project: ~120 MB NPP just from watchers.
Relationship to NtFC Pool Tag
Issues #45889 and #45921 identify pool tag NtFC (NTFS File Control Block) as the dominant NPP consumer. ReadDirectoryChangesW triggers NTFS FCB allocation for each watched directory. The watchers create a sustained baseline of FCB allocations that compound with other file I/O patterns.
Additional concern: no depth exclusions
The chokidar call sites do not appear to use ignored patterns for common large directories:
// Observed pattern in bundle
Aa.watch(L, {
persistent: true,
ignoreInitial: true,
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 200 },
ignorePermissionErrors: true
// no 'ignored' for node_modules, .git, dist, build, target
});
Suggested Fix
- Set explicit depth limits on project file watchers (depth 3-5 covers most source trees)
- Exclude common large directories:
node_modules,.git,dist,build,target,vendor,__pycache__,.next,.nuxt - Consider polling mode on Windows for deep directories (
usePolling: truewith reasonable interval) - Share watchers across processes — subagents shouldn't each create their own project watchers
Environment
- claude.exe v2.1.109 (Bun binary), Windows 11 Pro 10.0.26200
- Analysis method: PE section extraction + chokidar call site enumeration
Related Issues
- #45889 (NtFC pool tag NPP leak — same root cause, different reporter)
- #45921 (VM temp file NPP leak — different vector, same pool tag)
- #43548 (closed — extreme Other I/O rate causing NPP exhaustion)
- #42169 (overall resource exhaustion report including NPP)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗