Plugin PreToolUse hook matcher removed from cache/hooks.json still fires after full restart
Summary
A plugin's PreToolUse hook matcher change made directly in the installed cache copy (~/.claude/plugins/cache/<repo>/<plugin>/<version>/hooks/hooks.json) is not honored — the tool call is still blocked by the removed matcher's hook — even after a full process restart (not just /compact).
Documentation cross-check
Per plugin-marketplaces.md: "Claude Code copies each installed plugin into the local versioned plugin cache at ~/.claude/plugins/cache... Claude Code loads the plugin through those links at every startup" (for link-mode) — for the default copy mode, the clear implication (and the only copy Claude Code is documented to load from) is ~/.claude/plugins/cache/..., not ~/.claude/plugins/marketplaces/... (which is described purely as the marketplace source/update reference).
Steps to reproduce
- Install a marketplace plugin that registers a
PreToolUsehook with"matcher": "WebFetch"inhooks/hooks.json(in our case,scottconverse/context-mode). - Confirm the hook fires (e.g. it denies/blocks the
WebFetchtool call with a custom error). - Edit
~/.claude/plugins/cache/<repo>/<plugin>/<version>/hooks/hooks.jsondirectly: remove theWebFetchmatcher block entirely (4-line JSON block, no other change). - Fully quit Claude Code (close the terminal) and start a brand-new session (
claude) in the same project directory — not/compact, a full new process. - Call the
WebFetchtool.
Expected
WebFetch succeeds — the hook that would have blocked it no longer has a matching PreToolUse matcher entry in the file Claude Code is documented to load from (plugins/cache/...).
Actual
WebFetch is still blocked, with the exact same custom error message the (now-removed) hook was producing before the edit — evidence the same hook logic is still running, not that some unrelated permission is denying it.
Confirmed via direct Read of plugins/cache/.../hooks/hooks.json immediately before and after the restart: file content is stable, byte-identical to the edited version, WebFetch matcher confirmed absent both times.
What we ruled out
- Stale in-process cache surviving
/compact: ruled out — behavior is identical after a full newclaudeprocess (not just/compact), which we would expect to force a fresh read. - A second copy of
hooks.jsoninplugins/marketplaces/<repo>/hooks/hooks.json(the marketplace source clone) still containing theWebFetchmatcher: found and noted, but per docs this directory is the source used for marketplace refresh/updates, not the runtime load path — the runtime load path (plugins/cache/...) is the one we edited and verified correct. - A permissions-level
denyrule forWebFetchin the user's own~/.claude/settings.json: checked —WebFetchis explicitly in theallowlist there, notdeny. - A second plugin-local
settings.json(plugins/cache/.../<version>/.claude/settings.json): checked — contains unrelatedsudo/rm -rf/.envdeny rules only, nothing referencingWebFetch.
Open question for Anthropic
Does Claude Code actually load plugin hooks from plugins/cache/... at every session start as documented, or is there an additional caching layer (e.g. a compiled/hashed hook-registration index, keyed by plugin version rather than by live file content) that isn't invalidated by a direct edit to hooks.json in the cache copy? If the latter, what is the supported way to make an edited hooks.json take effect without bumping the plugin version or reinstalling?
Environment
- Claude Code CLI, Windows 11
- Plugin:
scottconverse/context-modev1.6.1, installed via marketplacescottconverse-context-mode
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗