PreToolUse:Bash hook shows 'hook error' when all handlers return None (exit 0, no output)

Resolved 💬 3 comments Opened Apr 9, 2026 by hey-jhong Closed Apr 13, 2026

Description

When a PreToolUse hook is configured for the Bash matcher, Claude Code displays "PreToolUse:Bash hook error" for every Bash tool call, even when the hook exits with code 0 and produces no output (meaning "allow").

Expected behavior

When a hook exits with 0 and no stdout/stderr, it should be treated as a silent "allow" — no error label should be shown in the UI.

Actual behavior

"PreToolUse:Bash hook error" is displayed before every Bash command output, regardless of the hook's exit code or output.

Steps to reproduce

  1. Add a PreToolUse hook with "matcher": "Bash" in .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 my_hook.py PreToolUse",
            "statusMessage": "checking..."
          }
        ]
      }
    ]
  }
}
  1. The hook script checks the command and returns nothing (exit 0) for most commands, only outputting JSON for specific blocked/warned commands.
  1. Every Bash tool call shows "PreToolUse:Bash hook error" in the UI.

Additional context

  • Hooks for Edit|Write matcher work correctly — they show "additional context" or "blocking error" only when the hook outputs JSON.
  • Outputting explicit {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow"}} also shows "hook error".
  • The hook is used for branch switch protection and destructive command detection — it needs to run for all Bash commands but only produces output for specific patterns.

Environment

  • Claude Code CLI (macOS)
  • Claude Opus 4.6 (1M context)

View original on GitHub ↗

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