Claude Code Hooks: Automating Your Development Workflow
Deep dive into Claude Code hooks — pre and post tool execution hooks that let you enforce linting, run tests automatically, validate changes, and build custom CI-like workflows.
What Are Claude Code Hooks?
Claude Code hooks are user-defined shell commands that execute automatically at specific points during Claude Code's agentic workflow. They let you inject custom logic before or after Claude Code performs actions — similar to git hooks, but for AI-assisted development.
Hooks solve a fundamental problem: you want Claude Code to follow specific procedures (run linting after every edit, validate JSON schemas, check for secrets) but you do not want to repeat these instructions in every conversation. Hooks make these procedures automatic and enforceable.
Hook Types
Claude Code supports hooks at several execution points:
PreToolUse Hooks
PreToolUse hooks run before Claude Code executes a tool. They can inspect the planned action and either allow it, modify it, or block it.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hook": "python3 .claude/hooks/pre-edit-check.py"
}
]
}
}
Use cases:
- Block edits to protected files — Prevent Claude from modifying migration files, lock files, or generated code
- Validate before write — Check that new files follow naming conventions
- Security scanning — Scan Bash commands for dangerous operations before execution
PostToolUse Hooks
PostToolUse hooks run after a tool completes. They can inspect the result and trigger follow-up actions.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hook": "npx eslint --fix $CLAUDE_FILE_PATH"
},
{
"matcher": "Write",
"hook": "npx prettier --write $CLAUDE_FILE_PATH"
}
]
}
}
Use cases:
- Auto-format after edits — Run Prettier, Black, or gofmt on every modified file
- Lint checking — Run ESLint or Ruff after file changes
- Test execution — Automatically run relevant tests after code changes
- Schema validation — Validate JSON/YAML files after writing
Notification Hooks
Notification hooks trigger when specific events occur, such as Claude Code requesting user input or completing a long task.
{
"hooks": {
"Notification": [
{
"matcher": "",
"hook": "terminal-notifier -message '$CLAUDE_NOTIFICATION' -title 'Claude Code'"
}
]
}
}
Configuring Hooks
Hooks are defined in .claude/settings.json at the project level or ~/.claude/settings.json globally.
Full Configuration Example
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hook": "python3 .claude/hooks/validate-bash-command.py",
"timeout": 5000
},
{
"matcher": "Edit|Write",
"hook": ".claude/hooks/check-protected-files.sh"
}
],
"PostToolUse": [
{
"matcher": "Edit",
"hook": ".claude/hooks/post-edit.sh"
},
{
"matcher": "Write",
"hook": ".claude/hooks/post-write.sh"
}
],
"Notification": [
{
"matcher": "",
"hook": "notify-send 'Claude Code' '$CLAUDE_NOTIFICATION'"
}
]
}
}
Matcher Patterns
The matcher field determines which tool triggers the hook. It supports:
- Exact match:
"Edit"— only Edit tool calls - Pipe-separated alternatives:
"Edit|Write"— both Edit and Write - Empty string:
""— matches all tools/events
Environment Variables Available to Hooks
| Variable | Description |
|---|---|
$CLAUDE_TOOL_NAME |
The tool being called (Read, Edit, Write, Bash, etc.) |
$CLAUDE_FILE_PATH |
The file being operated on (for file tools) |
$CLAUDE_BASH_COMMAND |
The command being executed (for Bash tool) |
$CLAUDE_NOTIFICATION |
The notification message (for Notification hooks) |
$CLAUDE_PROJECT_DIR |
The project root directory |
Practical Hook Recipes
Recipe 1: Auto-Format on Every Edit
#!/bin/bash
# .claude/hooks/post-edit.sh
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE" 2>/dev/null
npx eslint --fix "$FILE" 2>/dev/null
;;
*.py)
ruff format "$FILE" 2>/dev/null
ruff check --fix "$FILE" 2>/dev/null
;;
*.go)
gofmt -w "$FILE" 2>/dev/null
;;
*.rs)
rustfmt "$FILE" 2>/dev/null
;;
esac
This hook auto-formats every file Claude Code edits, ensuring consistent style without Claude needing to worry about formatting.
Recipe 2: Protect Critical Files
#!/bin/bash
# .claude/hooks/check-protected-files.sh
PROTECTED_PATTERNS=(
"*.lock"
"package-lock.json"
"yarn.lock"
"migrations/versions/*.py"
".env*"
"*.pem"
"*.key"
)
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$CLAUDE_FILE_PATH" == $pattern ]]; then
echo "BLOCKED: Cannot modify protected file: $CLAUDE_FILE_PATH"
exit 1
fi
done
exit 0
When a PreToolUse hook exits with a non-zero code, Claude Code blocks the tool execution and shows the hook's output to the model, which then adjusts its approach.
Recipe 3: Run Tests After Changes
#!/bin/bash
# .claude/hooks/post-edit-test.sh
FILE="$CLAUDE_FILE_PATH"
# Find and run related test files
if [[ "$FILE" == *.py ]]; then
TEST_FILE="${FILE/app\//tests/test_}"
if [[ -f "$TEST_FILE" ]]; then
pytest "$TEST_FILE" -x --tb=short -q 2>&1 | tail -5
fi
elif [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
TEST_FILE="${FILE%.ts*}.test${FILE##*.ts}"
if [[ -f "$TEST_FILE" ]]; then
npx vitest run "$TEST_FILE" --reporter=verbose 2>&1 | tail -10
fi
fi
Recipe 4: Secret Detection
#!/bin/bash
# .claude/hooks/scan-secrets.sh
FILE="$CLAUDE_FILE_PATH"
# Skip binary files and known safe patterns
if file "$FILE" | grep -q "binary"; then
exit 0
fi
# Check for common secret patterns
PATTERNS=(
"sk-[a-zA-Z0-9]{20,}"
"AKIA[0-9A-Z]{16}"
"ghp_[a-zA-Z0-9]{36}"
"-----BEGIN (RSA |EC )?PRIVATE KEY-----"
"password\s*=\s*["'][^"']{8,}["']"
)
for pattern in "${PATTERNS[@]}"; do
if grep -qP "$pattern" "$FILE" 2>/dev/null; then
echo "BLOCKED: Potential secret detected in $FILE matching pattern: $pattern"
exit 1
fi
done
exit 0
Hook Execution Order and Error Handling
Execution Order
- PreToolUse hooks run sequentially in the order they are defined
- If any PreToolUse hook exits non-zero, the tool call is blocked
- If all PreToolUse hooks pass, the tool executes
- PostToolUse hooks run sequentially after the tool completes
- PostToolUse hook failures are reported but do not undo the tool execution
Timeout Handling
Hooks have a configurable timeout (default: 60 seconds). If a hook exceeds its timeout, it is killed and treated as a failure for PreToolUse (blocks the action) or a warning for PostToolUse.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hook": "npm test -- --timeout 30000",
"timeout": 45000
}
]
}
}
Hook Output
Hooks can write to stdout and stderr. This output is captured and fed back to Claude Code's model, allowing it to react to hook results. For example, if a linting hook reports errors, Claude Code will see those errors and can fix them in its next tool call.
Hooks vs CLAUDE.md Instructions
Both hooks and CLAUDE.md instructions influence Claude Code's behavior, but they work differently:
| Aspect | CLAUDE.md | Hooks |
|---|---|---|
| Enforcement | Advisory (model follows them but can deviate) | Mandatory (PreToolUse hooks block execution) |
| Execution | Interpreted by the AI model | Executed as shell commands |
| Timing | Read at session start | Run at each tool call |
| Reliability | High but not guaranteed | Guaranteed (scripts run regardless) |
Use CLAUDE.md for: coding conventions, architecture guidelines, style preferences
Use hooks for: formatting enforcement, security scanning, file protection, automated testing
The combination is powerful: CLAUDE.md tells Claude Code how to write code, and hooks verify that the code meets your standards after every change.
Team Workflow with Hooks
When your hooks are committed to the repository in .claude/settings.json and .claude/hooks/, every team member who uses Claude Code gets the same automated checks. This creates a consistent development experience:
- A developer asks Claude Code to implement a feature
- Claude Code writes the code
- PostToolUse hooks automatically format it and run linting
- If linting fails, Claude Code sees the errors and fixes them
- The developer reviews clean, formatted, validated code
This is essentially a local CI pipeline that runs on every AI-generated edit.
Conclusion
Claude Code hooks transform your AI coding assistant from a tool that follows suggestions into one that enforces standards. By combining PreToolUse hooks (for protection and validation) with PostToolUse hooks (for formatting and testing), you create guardrails that ensure Claude Code's output meets your team's quality bar automatically, every time.
NYC News
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.