[BUG] Plugin-dir watcher idle poll walks the whole CLAUDE_CODE_PLUGIN_DIRS folder on the main thread every 30 s, freezing input

Status Open
Reported on v2.1.285
Maintainer reply None cached
Activity 0 comments · opened Oct 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When a plugin is loaded from CLAUDE_CODE_PLUGIN_DIRS (a local function-hook mod), the plugin-dir watcher switches to idle polling about 60 s after the session goes idle, and polls every 30 s after that. Each poll walks the whole plugin folder synchronously on the main thread. Until the walk ends, the TUI is frozen: typed keys show up only after it finishes.

The walk includes gitignored folders. My plugin has a gitignored evals/out/ with eval results: 26,142 of the folder's 26,264 files (258 MB). Each poll blocks the event loop for 2–5 s. In long-running sessions I sampled the main thread's state every ~60 ms: it was CPU-bound for 3–7 s, every 30 s (e.g. 09:42:05.6, 09:42:35.6, 09:43:35.6), while the rest of the machine was mostly idle (~17% CPU) and the thread was not paging.

What Should Happen?

Watching a plugin folder should not block input. For example, walk off the main thread or incrementally, use native recursive fs.watch (supported on Windows), skip gitignored folders and node_modules, or watch only the paths a plugin loads from (.claude-plugin/, hooks/, skills/, agents/, commands/).

Error Messages/Logs

From claude --debug, idle session, nothing typed. The polling line is followed directly by event-loop stalls:

06:31:04.510Z [DEBUG] plugin-dir watch on C:\Users\user\.claude\local-plugins\claude-compact: idle, polling every 30000ms
06:31:09.622Z [WARN] [event-loop-stall] blocked for 1230ms monotonic (wall drift 1230ms, clock jump 0ms, expected 200ms). Total stalls: 1
06:31:10.265Z [WARN] [event-loop-stall] blocked for 3372ms monotonic (wall drift 3372ms, clock jump 0ms, expected 200ms). Total stalls: 2, cumulative: 4602ms

Minimal mod plus 26,000 empty files (repro below):

06:33:38.808Z [DEBUG] plugin-dir watch on C:\Users\user\AppData\Local\Temp\cc-lag\trivial-mod-big: idle, polling every 30000ms
06:33:41.331Z [WARN] [event-loop-stall] blocked for 735ms monotonic ...
06:33:41.920Z [WARN] [event-loop-stall] blocked for 643ms monotonic ...

Steps to Reproduce

  1. Create a minimal function-hook plugin:
  • .claude-plugin/plugin.json: {"name":"trivial-mod"}
  • hooks/hooks.json: {"modules":["./m.ts"]}
  • hooks/m.ts: export const register = (on) => { on('session.compact', async ($, e, next) => next(e)); };
  1. Add ~26,000 empty files anywhere inside it, e.g. evals/out/0..25999.txt. The folder may be gitignored.
  2. Run claude --debug with CLAUDE_CODE_PLUGIN_DIRS=<that folder>. Type nothing.
  3. About 60–70 s after launch the debug log shows plugin-dir watch …: idle, polling every 30000ms, followed by [event-loop-stall] warnings. Keys typed during the stall appear only after it ends.

Control: the same plugin without the 26,000 files gives no stalls. The same launch with "disableAllHooks": true gives no stalls either.

Claude Model

Not model-related.

Is this a regression?

Effectively yes. 2.1.285 doesn't load function-hook modules unless CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 (debug: hooks module … not loaded: … early access), so it never starts the watcher and my sessions were smooth. 2.1.291 and 2.1.292 load the module, start the watcher, and freeze. When the module is loaded, 2.1.285 stalls on the same large folder too, so the walk itself is not new.

Last Working Version

2.1.285 (default settings)

Claude Code Version

2.1.292 (Claude Code), native install. Also seen on 2.1.291.

Platform

Anthropic API

Operating System

Windows 10 Pro 22H2 (10.0.19045.6456), 16 logical cores, 16 GB RAM

Terminal/Shell

Windows Terminal, PowerShell. Hooks run through Git Bash.

Additional Information

How this was isolated:

  • A ConPTY harness timed each keystroke's echo and sampled the claude.exe main-thread state every 100–200 ms. All runs were idle, with no model turns.
  • Ruled out: other processes' CPU and memory load, transcript size, the status-line command, hook child processes, and ** deny-rule globbing (#98023).
  • Same family as other main-thread filesystem walks: #98023, #82988, #99976.

Workaround: keep large folders, such as eval outputs, out of any folder listed in CLAUDE_CODE_PLUGIN_DIRS.

View original on GitHub ↗