Claude Code Hooks Cheat Sheet

Quick reference · Print this page (Ctrl+P) · github.com/yurukusa/cc-safe-setup

Hook Lifecycle

Prompt → PreToolUse → Tool Executes → PostToolUse → Stop
         ↑ block here                  ↑ check here    ↑ log here

Exit Codes

CodeMeaning
0Allow (or no opinion)
2Block — tool call cancelled
otherError (treated as allow)

Hook Events

EventMatcherUse
PreToolUseBashBlock commands
PostToolUseEdit|WriteSyntax check
PostToolUse(empty)All tools
Stop(empty)Session end
UserPromptSubmitValidate input

settings.json Structure

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "~/.claude/hooks/guard.sh"
      }]
    }]
  }
}

Minimal Block Hook

#!/bin/bash
COMMAND=$(cat | jq -r '.tool_input.command // empty')
[ -z "$COMMAND" ] && exit 0
if echo "$COMMAND" | grep -qE 'PATTERN'; then
    echo "BLOCKED: reason" >&2
    exit 2
fi
exit 0

Auto-Approve Hook

#!/bin/bash
COMMAND=$(cat | jq -r '.tool_input.command // empty')
[ -z "$COMMAND" ] && exit 0
if echo "$COMMAND" | grep -qE '^\s*git\s+(status|log|diff)'; then
    jq -n '{
      "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow"
      }
    }'
fi
exit 0

PostToolUse Syntax Check

#!/bin/bash
FILE=$(cat | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
case "${FILE##*.}" in
  py) python3 -m py_compile "$FILE" 2>&1 ;;
  sh) bash -n "$FILE" 2>&1 ;;
  json) jq empty "$FILE" 2>&1 ;;
  js) node --check "$FILE" 2>&1 ;;
esac
exit 0

Modify Input

#!/bin/bash
# Strip comments from bash commands
COMMAND=$(cat | jq -r '.tool_input.command // empty')
CLEAN=$(echo "$COMMAND" | sed '/^#/d; /^$/d')
[ "$CLEAN" = "$COMMAND" ] && exit 0
jq -n --arg cmd "$CLEAN" '{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "updatedInput": {"command": $cmd}
  }
}'

stdin JSON Reference

EventKey Fields
PreToolUse (Bash).tool_input.command
PreToolUse (Edit).tool_input.file_path
PostToolUse.tool_input.file_path
Stop.stop_reason
UserPromptSubmit.prompt

Test a Hook

# Manual test
echo '{"tool_input":{"command":"rm -rf /"}}' \
  | bash ~/.claude/hooks/guard.sh
echo $?  # 2 = blocked

# Auto-test
npx cc-hook-test ~/.claude/hooks/guard.sh

Quick Setup Commands

npx cc-safe-setupInstall 8 hooks
--create "desc"Generate hook
--auditSafety score
--lintConfig analysis
--doctorDiagnose issues
--watchLive dashboard
--statsBlock statistics
--verifyTest all hooks
--exportShare with team

Common Patterns

BlockPattern
rm -rf /rm\s+(-[rf]+\s+)*/
force pushgit\s+push.*--force
push maingit\s+push.*main
.env commitgit\s+add.*\.env
git resetgit\s+reset\s+--hard
DB wipemigrate:fresh|DROP\s+DB