Local plugin hooks match but never execute (isLocal: true)
Summary
Local plugins (isLocal: true) have their hooks matched by Claude Code but the hooks are never actually executed. This appears to be the same root cause as #11509, #11939, and #12151.
Environment
- Claude Code version: 2.0.64
- Platform: macOS Darwin 24.6.0
- Plugin installation: Local (
isLocal: trueininstalled_plugins.json)
Steps to Reproduce
- Create a local plugin with hooks in
hooks/hooks.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "my-plugin hook queue-synthesis",
"async": true
}
]
}
]
}
}
-
Install the plugin locally (results in
isLocal: true) -
Run Claude Code and observe debug logs
Expected Behavior
The Stop hook should execute when Claude finishes responding.
Actual Behavior
Debug logs show:
Loaded hooks from standard location for plugin totalrecall: /Users/.../.claude/plugins/cache/totalrecall-local/totalrecall/3.0.5/hooks/hooks.json
Loading hooks from plugin: totalrecall
Getting matching hook commands for Stop with query: undefined
Found 1 hook matchers in settings
Matched 1 unique hooks for query "no match query" (1 before deduplication)
But no execution follows. The command is never run. No process spawned. Manual execution of the same command works perfectly:
echo '{"session_id":"test","transcript_path":"/path/to/transcript.jsonl"}' | my-plugin hook queue-synthesis
# Returns: {"queued":true,"queue_id":1,"message":"Session chunk queued for synthesis"}
Workaround
Moving hooks from the plugin's hooks/hooks.json to the user's ~/.claude/settings.json works:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/my-plugin hook queue-synthesis",
"timeout": 30000
}
]
}
]
}
}
This confirms the hook configuration is correct - only the plugin hook execution path is broken.
Evidence from installed_plugins.json
"my-plugin@my-plugin-local": {
"isLocal": true
}
Related Issues
- #10875 - Plugin hooks JSON output not captured (CLOSED)
- #11509 - Local file-based plugin hooks never execute
- #11939 - Local plugin SessionStart hooks don't execute (CLOSED as dup)
- #12151 - Plugin hook output not passed to agent (OPEN)
Impact
This bug completely breaks the hook functionality for any locally-developed plugin, forcing developers to either:
- Ask users to manually copy hooks to their settings.json
- Publish to git and install from remote (changes
isLocaltofalse)
Neither is acceptable for local plugin development workflow.
Investigation Details
How We Discovered This
We noticed that our TotalRecall memory plugin wasn't capturing any conversation data from Dec 17-18, despite:
- MCP server working correctly
- 596 transcript files existing in
~/.claude/projects/ - Hooks being loaded and matched according to debug logs
Root Cause Diagnosis
Using debug logs at ~/.claude/debug/, we traced the issue:
-
Hooks ARE loaded:
Loaded hooks from standard location for plugin totalrecall -
Hooks ARE matched:
Matched 1 unique hooks for query - Hooks are NOT executed: No subsequent execution logs, no process spawned
The key differentiator is isLocal: true in installed_plugins.json. This flag is set when installing plugins locally vs from a git remote.
Workaround Implementation
We moved critical hooks from the plugin's hooks/hooks.json to the user's ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/Users/alexander/.cargo/bin/totalrecall hook queue-synthesis",
"timeout": 30000
}
]
}
]
}
}
Important: Use absolute paths in settings.json since it's not relative to the plugin directory.
Verification
After applying the workaround:
$ totalrecall dream status
Queue Statistics:
Pending: 1 # Was 0 before
Processing: 0
Completed: 0
Failed: 0
The Stop hook now executes and queues transcripts for synthesis.
Additional Bug Found During Investigation
We also discovered a separate bug in our plugin where the JSONL parser expected type: "message" but Claude Code transcripts use type: "user" and type: "assistant". This is unrelated to the hooks execution bug but worth noting for other plugin developers parsing transcript files.
Found 3 possible duplicate issues:
- https://github.com/anthropics/claude-code/issues/11939
- https://github.com/anthropics/claude-code/issues/11509
- https://github.com/anthropics/claude-code/issues/10225
This issue will be automatically closed as a duplicate in 3 days.
- If your issue is a duplicate, please close it and 👍 the existing issue instead
- To prevent auto-closure, add a comment or 👎 this comment
🤖 Generated with Claude Code
Experiencing the same on 2.0.73 on Ubuntu 24 via WSL and Claude Code Web.
This seems to be an issue related to upgrading beyond 2.0.70. To fix, uninstall all plugins and clear prior installation from plugin cache at ~/.claude/plugins/cache and then reinstall the plugins freshly and they should work. Note I also updated to 2.0.74 before starting the next session.
https://github.com/anthropics/claude-code/issues/12634#issuecomment-3674215064 https://github.com/anthropics/claude-code/issues/11509
Also having this issue 2.0.76 macos terminal. Specifically, post tool use hooks are running but NOT blocking claude code when installed at project level. Same hooks work fine at user level.
This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.
do not autoclave, this is still happening
Closing for now — inactive for too long. Please open a new issue if this is still relevant.