Plugin PreToolUse hook matcher removed from cache/hooks.json still fires after full restart

Status Fixed / completed
Maintainer reply None cached
Activity 2 comments · opened Aug 28, 2026 · closed Aug 28, 2026

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

  1. Install a marketplace plugin that registers a PreToolUse hook with "matcher": "WebFetch" in hooks/hooks.json (in our case, scottconverse/context-mode).
  2. Confirm the hook fires (e.g. it denies/blocks the WebFetch tool call with a custom error).
  3. Edit ~/.claude/plugins/cache/<repo>/<plugin>/<version>/hooks/hooks.json directly: remove the WebFetch matcher block entirely (4-line JSON block, no other change).
  4. 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.
  5. Call the WebFetch tool.

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 new claude process (not just /compact), which we would expect to force a fresh read.
  • A second copy of hooks.json in plugins/marketplaces/<repo>/hooks/hooks.json (the marketplace source clone) still containing the WebFetch matcher: 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 deny rule for WebFetch in the user's own ~/.claude/settings.json: checked — WebFetch is explicitly in the allow list there, not deny.
  • A second plugin-local settings.json (plugins/cache/.../<version>/.claude/settings.json): checked — contains unrelated sudo/rm -rf/.env deny rules only, nothing referencing WebFetch.

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-mode v1.6.1, installed via marketplace scottconverse-context-mode

View original on GitHub ↗

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