Plugin cache dir deleted mid-session when git commits change HEAD — causes infinite Stop hook loop

Resolved 💬 2 comments Opened Mar 29, 2026 by tt2work Closed Mar 29, 2026

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:

  1. Create a new cache dir at the new HEAD hash
  2. Update installed_plugins.json to point to the new path
  3. Run cache cleanup (Rh4) which deletes dirs not in installed_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

  1. Install any plugin with hooks (e.g., hookify from claude-plugins-official)
  2. Start a session in a git repository
  3. Make one or more git commits during the session
  4. Launch a subagent (via Agent tool) or resume the session (/compact)
  5. The subagent/new process re-caches plugins at the new HEAD hash and deletes the old cache dir
  6. 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 8908a582f80d was deleted when a subagent started and cached at e768220df2d3

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 gitCommitSha instead 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

View original on GitHub ↗

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