README.md 3.9 KB

Hooks

Claude Code hooks allow you to run custom scripts at key workflow points.

Configuration

Add hooks to .claude/settings.json or .claude/settings.local.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": ["bash hooks/security-check.sh $TOOL_INPUT"]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": ["bash hooks/post-edit.sh $FILE_PATH"]
      }
    ]
  }
}

Hook Types

Hook Trigger Use Case
PreToolUse Before tool execution Validate inputs, security checks
PostToolUse After tool execution Run tests, linting, notifications
Notification On specific events Alerts, logging
Stop When Claude stops Cleanup, summaries

Examples

1. Security Check (PreToolUse)

Detect dangerous patterns before execution:

#!/bin/bash
# hooks/security-check.sh
# Detects: eval, exec, os.system, pickle, SQL injection patterns

INPUT="$1"

PATTERNS=(
  "eval("
  "exec("
  "os.system("
  "subprocess.call.*shell=True"
  "pickle.loads"
  "__import__"
  "rm -rf /"
  "DROP TABLE"
  "; DROP"
)

for pattern in "${PATTERNS[@]}"; do
  if echo "$INPUT" | grep -q "$pattern"; then
    echo "SECURITY WARNING: Detected potentially dangerous pattern: $pattern"
    exit 1
  fi
done

exit 0

2. Auto-Lint (PostToolUse)

Run linter after file edits:

#!/bin/bash
# hooks/post-edit.sh

FILE="$1"
EXT="${FILE##*.}"

case "$EXT" in
  ts|tsx|js|jsx)
    npx eslint --fix "$FILE" 2>/dev/null
    ;;
  py)
    ruff check --fix "$FILE" 2>/dev/null
    ;;
  md)
    # Optional: markdown lint
    ;;
esac

3. Auto-Test (PostToolUse)

Run tests after code changes:

#!/bin/bash
# hooks/post-test.sh

FILE="$1"

# Only run for source files
if [[ "$FILE" == *"/src/"* ]]; then
  # Find and run related test
  TEST_FILE="${FILE/src/tests}"
  TEST_FILE="${TEST_FILE/.ts/.test.ts}"

  if [[ -f "$TEST_FILE" ]]; then
    npm test -- "$TEST_FILE" --passWithNoTests
  fi
fi

4. Commit Message Hook

Ensure commit messages follow convention:

#!/bin/bash
# hooks/commit-msg.sh

MSG="$1"

# Conventional commits pattern
PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}"

if ! echo "$MSG" | grep -qE "$PATTERN"; then
  echo "ERROR: Commit message doesn't follow conventional commits format"
  echo "Expected: type(scope): description"
  echo "Example: feat(auth): add login endpoint"
  exit 1
fi

Settings Example

Full hooks configuration:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": ["bash hooks/security-check.sh $TOOL_INPUT"]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          "bash hooks/post-edit.sh $FILE_PATH",
          "bash hooks/post-test.sh $FILE_PATH"
        ]
      }
    ]
  }
}

Variables Available

Variable Description
$TOOL_INPUT Full input to the tool
$TOOL_OUTPUT Output from tool (PostToolUse only)
$FILE_PATH Path to file being modified
$TOOL_NAME Name of tool being called

Best Practices

  1. Keep hooks fast - They run synchronously and block Claude
  2. Exit 0 for success - Non-zero exits halt execution
  3. Log sparingly - Output goes to Claude's context
  4. Use matchers - Only run hooks for relevant tools
  5. Test locally first - Debug before enabling in Claude

Security Patterns to Detect

From Anthropic's security-guidance plugin:

Pattern Risk
eval(, exec( Code injection
os.system(, subprocess.call.*shell=True Command injection
pickle.loads Deserialization attack
__import__ Dynamic import abuse
innerHTML, document.write XSS
DROP TABLE, ; DROP SQL injection
rm -rf / Destructive commands