7 Common Hook Mistakes

Each one has caused real incidents. Each one has a simple fix.

1
Bash syntax error = blocks ALL tools

Bash returns exit code 2 for syntax errors. Claude Code treats exit 2 as "block." A broken script silently blocks every tool call.

# This script has a syntax error (missing 'then'):
if [ -z "$INPUT" ]
fi
# bash exits with code 2 → Claude blocks the tool
Fix: Always run bash -n hook.sh before deploying. Or: npx cc-safe-setup --validate
2
Using matcher "" during development

Matcher "" applies to ALL tools (Bash, Read, Write, Edit, Grep, Agent). If your hook breaks, you can't even read files to debug it.

// DON'T:
{"matcher": "", "hooks": [...]}

// DO:
{"matcher": "Bash", "hooks": [...]}
Fix: Use "Bash" during development. Only use "" for well-tested production hooks.
3
Missing jq = silent failure

Most hooks use jq to parse JSON stdin. If jq isn't installed, the hook silently fails and does nothing.

# This silently fails without jq:
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
Fix: Run npx cc-safe-setup --doctor — it checks jq installation first.
4
Forgetting to exit 0 at the end

If the last command in your script fails, bash inherits that exit code. If it happens to be 2, you accidentally block.

#!/bin/bash
INPUT=$(cat)
grep -q "something" <<< "$INPUT"  # returns 1 if no match
# Script exits with grep's exit code (1) — hook error
Fix: Always end with explicit exit 0
5
Matching too broadly with grep

Matching rm in the command string catches directory names like soft-hold-enrollment that contain "rm".

# DON'T: matches "rm" in directory names
grep -q "rm" <<< "$COMMAND"

# DO: match rm as a word boundary with flags
grep -qE '\brm\s+-[rf]' <<< "$COMMAND"
Fix: Use \b word boundaries and \s+ whitespace in regex patterns.
6
Not handling empty input

Hooks receive {} for some edge cases. If your script doesn't handle empty fields, it may crash or block unexpectedly.

# Safe pattern:
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0  # exit early on empty
Fix: Always check for empty values and exit 0 early.
7
Hook takes too long

Hooks have a timeout. If your hook does network calls or heavy processing, it may time out and silently fail. On Windows, Python hooks take 200-500ms to start.

Fix: Keep hooks fast. Use bash instead of Python. Avoid network calls. Run npx cc-safe-setup --benchmark to measure.