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

  1. Create a plugin with hooks/hooks.json containing 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
          }
        ]
      }
    ]
  }
}
  1. Install the plugin and restart Claude Code
  2. 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

  1. /hooks menu confirms the drop: Running /hooks in 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
  1. 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 with async: false, timeout: 10. After restart, ran multiple Bash/Write/Edit tool calls — log file was never created.
  1. Lifecycle hooks from the same plugin work: SessionStart command hook in the same hooks.json fires every session and creates state files. SessionEnd, Stop, PreCompact, UserPromptSubmit all fire their command hooks correctly.
  1. 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).

View original on GitHub ↗

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