Hook error messages shown on every tool call even when hooks exit 0

Resolved 💬 3 comments Opened Mar 16, 2026 by noether-current Closed Mar 16, 2026

Description

All three hook types (PreToolUse, PostToolUse, UserPromptSubmit) display "hook error" messages in the transcript on every tool call, even when the hook scripts exit 0 for non-matching commands. The hooks function correctly (they format files, validate commits, block bad inputs) — but Claude Code reports them as errors anyway.

Expected behavior

Hooks that exit 0 should be silent. "hook error" should only appear for non-zero, non-2 exit codes.

Actual behavior

Every Bash call shows two PreToolUse:Bash hook error messages (one per hook). Every Edit/Write shows PostToolUse:Edit hook error. Every user prompt shows UserPromptSubmit hook error. No detail is provided — just "hook error".

Actions still proceed (not blocked), and the hooks execute their logic correctly.

Hook configuration

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/validate-commit.sh" },
          { "type": "command", "command": "bash .claude/hooks/validate-issue.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/format-on-save.sh" }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/session-audit.sh", "timeout": 15 }
        ]
      }
    ]
  }
}

Example hook script (validate-commit.sh)

#!/usr/bin/env bash
# Blocks git commit without issue reference (#NN)
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
echo "$COMMAND" | grep -qE '^git commit' || exit 0
echo "$COMMAND" | grep -qE '\-\-amend|merge' && exit 0
if ! echo "$COMMAND" | grep -qP '#\d+'; then
  echo "Commit message must include issue reference (#NN)." >&2
  exit 2
fi
exit 0

All hooks follow the same pattern: early exit 0 for non-matching commands, exit 2 to block, exit 0 on success.

Manual testing

Running the hooks manually with sample JSON input returns exit 0 as expected:

echo '{"tool_input":{"command":"git status"}}' | bash .claude/hooks/validate-commit.sh
echo $?  # outputs: 0

Transcript excerpt

● Bash(git status -u)
  ⎿  PreToolUse:Bash hook error
  ⎿  PreToolUse:Bash hook error
  ⎿  On branch feat/playground-design-sandbox
     ...

● Update(~/Work/projects/workout/.claude/hooks/session-audit.sh)
  ⎿  Removed 1 line
  ⎿  PostToolUse:Edit hook error

Environment

  • Claude Code CLI (latest as of 2026-03-16)
  • Linux (WSL2, Ubuntu)
  • bash 5.x
  • jq installed (jq- — version string incomplete but functional)
  • Node 22.x

View original on GitHub ↗

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