Plugin cache dir deleted mid-session when git commits change HEAD — causes infinite Stop hook loop
Summary
Plugin cache directories are keyed to the first 12 characters of git rev-parse HEAD in the working repository (not the plugin/marketplace repo). When a session makes git commits, HEAD changes. Any new Claude Code process (subagent, session resume) that starts afterward will:
- Create a new cache dir at the new HEAD hash
- Update
installed_plugins.jsonto point to the new path - Run cache cleanup (
Rh4) which deletes dirs not ininstalled_plugins.json
The parent session's CLAUDE_PLUGIN_ROOT env var still points to the old (now deleted) hash. All subsequent hook calls fail with ENOENT.
When the failing hook is a Stop hook, the error is injected as a synthetic user message, which triggers a new assistant turn, which triggers the Stop hook again — creating an infinite loop that burns tokens until the session times out or is manually killed.
Reproduction
- Install any plugin with hooks (e.g.,
hookifyfromclaude-plugins-official) - Start a session in a git repository
- Make one or more
git commits during the session - Launch a subagent (via
Agenttool) or resume the session (/compact) - The subagent/new process re-caches plugins at the new HEAD hash and deletes the old cache dir
- The parent session's next hook call fails → infinite loop if it's a Stop hook
Observed behavior
- Session ran for 111 minutes in an infinite Stop hook error loop
- 1,329 API calls, ~302M cache-read tokens (~$456 wasted)
- The loop only terminated when the 5-hour session limit killed the process
- The model recognized the loop and tried to emit empty responses (zero-width spaces), but the harness kept cycling
Environment
- Claude Code 2.1.80 (Linux, Node.js v20.20.0)
- Plugin:
hookify@claude-plugins-official(Stop hook registered) - Working repo had 122 commits made during the session lifetime (March 20-27)
- Cache dir
8908a582f80dwas deleted when a subagent started and cached ate768220df2d3
Root causes (three independent issues)
1. Cache key is working repo HEAD, not plugin content
The cache path ~/.claude/plugins/cache/<marketplace>/<plugin>/<hash>/ uses the working repo's HEAD commit. Any git commit invalidates it. The key should be derived from the plugin content or the marketplace repo commit (which is already tracked as gitCommitSha in installed_plugins.json).
2. Cache cleanup deletes dirs used by running processes
Rh4 removes any cache dir not in the current installed_plugins.json. It doesn't check whether other processes have CLAUDE_PLUGIN_ROOT pointing to that dir. Subagent startups and session resumes trigger this cleanup.
3. Stop hook errors create infinite feedback loops
When a Stop hook fails, the error is injected as a user message, triggering a new turn, which triggers the Stop hook again. There is no circuit breaker. At minimum, consecutive identical hook errors should be suppressed after 2-3 occurrences. (Related: #3573, #10205)
Suggested fixes
- Cache key: Use plugin content hash or marketplace
gitCommitShainstead of working repo HEAD - Cache cleanup: Reference-count cache dirs or skip cleanup when sibling processes are active
- Hook errors: Suppress consecutive identical hook errors after N occurrences; never re-inject a Stop hook error as a user message that triggers a new turn
Related issues
- #3573 — Stop hook failure infinite loop (GitHub Actions)
- #10205 — Infinite loop when hooks enabled
- #16453 — Plugin cache grows indefinitely without cleanup
- #15642 — CLAUDE_PLUGIN_ROOT points to stale version after plugin update
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗