Chokidar file watchers with unlimited depth exhaust Windows Non-Paged Pool (NPP) kernel memory

Resolved 💬 2 comments Opened Apr 15, 2026 by JF10R Closed May 25, 2026

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 directories
  • dist/, 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

  1. Set explicit depth limits on project file watchers (depth 3-5 covers most source trees)
  2. Exclude common large directories: node_modules, .git, dist, build, target, vendor, __pycache__, .next, .nuxt
  3. Consider polling mode on Windows for deep directories (usePolling: true with reasonable interval)
  4. 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)

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗