Plugin hooks.json: command hooks silently dropped for PreToolUse/PostToolUse events
Resolved 💬 4 comments Opened Mar 15, 2026 by crombieman Closed Apr 12, 2026
Bug Description
Command hooks registered in a plugin's hooks/hooks.json for PreToolUse and PostToolUse events are silently dropped during plugin loading. Prompt hooks on the same events from the same plugin load and fire correctly.
Environment
- Claude Code version: 2.1.63 (VSCode extension)
- OS: Windows 10 Pro 10.0.19045
- Shell: bash (Git Bash)
- Plugin: custom plugin installed via
claude plugins install
Steps to Reproduce
- Create a plugin with
hooks/hooks.jsoncontaining both prompt and command hooks for PreToolUse:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "Check the tool input for safety."
},
{
"type": "command",
"command": "\"C:/Program Files/Git/bin/bash.exe\" \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/my-hook.sh\"",
"timeout": 30,
"async": false
}
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "\"C:/Program Files/Git/bin/bash.exe\" \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/post-hook.sh\"",
"timeout": 30,
"async": false
}
]
}
],
"SessionStart": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "\"C:/Program Files/Git/bin/bash.exe\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start\"",
"async": false
}
]
}
]
}
}
- Install the plugin and restart Claude Code
- Use any tool (Write, Edit, Bash, etc.)
Expected Behavior
- Both prompt hooks AND command hooks should fire for PreToolUse/PostToolUse events
- The command hook script should receive JSON on stdin and execute
Actual Behavior
- Prompt hooks fire correctly for PreToolUse (verified — we see prompt hook output every tool call)
- Command hooks are silently dropped — the script never executes, no log output, no errors
- Lifecycle event command hooks work fine from the same plugin: SessionStart, SessionEnd, Stop, PreCompact, UserPromptSubmit all fire their command hooks correctly
Diagnostic Evidence
/hooksmenu confirms the drop: Running/hooksin Claude Code shows:
- PreToolUse
Write|Edit: 2 hooks (both prompt hooks — the command hook is missing) - PreToolUse
Bash: 1 hook (prompt hook only — command hook missing) - PostToolUse: only shows hookify's hook, not our plugin's command hooks
- Minimal reproduction: Created a dead-simple test script that only appends a timestamp to a log file and returns
{}with exit 0. Registered it as both PreToolUse and PostToolUse command hooks withasync: false,timeout: 10. After restart, ran multiple Bash/Write/Edit tool calls — log file was never created.
- Lifecycle hooks from the same plugin work: SessionStart command hook in the same
hooks.jsonfires every session and creates state files. SessionEnd, Stop, PreCompact, UserPromptSubmit all fire their command hooks correctly.
- Script works manually: Running the hook script directly with piped JSON input works perfectly — the issue is that Claude Code never invokes it.
Impact
This prevents plugins from implementing tool-use guardrails (e.g., migration linting on Write, edit tracking on PostToolUse) via command hooks. The only workaround is prompt hooks (which can't run arbitrary logic) or moving command hooks to settings.json (which breaks the plugin's self-contained distribution model).
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗