| name | claude-hooks |
| description | Use when automating Claude Code workflows with hooks — PreToolUse (validate/block actions), PostToolUse (react to completions), Stop (enforce standards before finishing), and SessionStart (load context). Configure in .claude/settings.json. |
Claude Hooks
Overview
Hooks let you automate Claude Code: run tests automatically after edits, block dangerous operations, enforce quality gates before Claude declares done. Without hooks, Claude relies purely on its training and your instructions. With hooks, you build enforced guardrails and automation into the workflow itself.
When to Use
- Want Claude Code to auto-run tests after editing files
- Need to prevent certain risky operations (e.g.,
rm -rf, force push)
- Want to auto-format code whenever Claude edits files
- Need to log or audit all Claude Code actions
- Want to enforce quality checks before Claude finishes a task
Core Jobs
1. Hook Types
| Hook | When it fires | Use for |
|---|
SessionStart | Every new Claude Code session | Load context, greet, check state |
PreToolUse | Before ANY tool executes | Validate, block, or modify tool calls |
PostToolUse | After tool executes | React to changes, run follow-ups |
Stop | When Claude finishes responding | Enforce done-criteria before Claude stops |
2. Configuring Hooks (.claude/settings.json)
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "bash .claude/hooks/validate-bash.sh '$CLAUDE_TOOL_INPUT'"
}]
}],
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "bash .claude/hooks/post-edit.sh",
"async": true
}]
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "bash .claude/hooks/enforce-done.sh"
}]
}]
}
}
3. Practical Hook Examples
Block dangerous bash commands (PreToolUse):
#!/bin/bash
INPUT="$1"
if echo "$INPUT" | grep -qE "rm -rf|DROP TABLE|git push --force"; then
echo "BLOCKED: Destructive operation requires explicit confirmation"
exit 1
fi
exit 0
Auto-run tests after file edits (PostToolUse):
#!/bin/bash
CHANGED_FILE="$CLAUDE_TOOL_RESULT"
if [[ "$CHANGED_FILE" == *.py ]]; then
cd "$(git rev-parse --show-toplevel)"
python -m pytest tests/ -x -q --tb=short 2>&1 | tail -20
fi
Enforce test passing before finishing (Stop):
#!/bin/bash
if git diff --name-only | grep -q "\.py$"; then
echo "Running tests before declaring done..."
if ! python -m pytest tests/ -x -q; then
echo "Tests failing — cannot mark task complete"
exit 1
fi
fi
exit 0
4. Hook Environment Variables
Available in hook scripts:
CLAUDE_TOOL_NAME
CLAUDE_TOOL_INPUT
CLAUDE_TOOL_RESULT
CLAUDE_SESSION_ID
CLAUDE_PLUGIN_ROOT
Key Concepts
- PreToolUse hook — intercepts tool calls before execution; exit 1 = block, exit 0 = allow
- PostToolUse hook — runs after tool completion;
async: true means non-blocking
- Stop hook — runs when Claude finishes; exit 1 = force Claude to continue working
- Matcher — regex or tool name that determines when hook fires
- Hook chaining — multiple hooks can fire for the same event in sequence
Checklist
Key Outputs
.claude/settings.json: hooks configuration
.claude/hooks/ directory: shell scripts for each hook behavior
- Documented hook behaviors in CLAUDE.md
Output Format
- 🔴 Critical — no hooks configured (no safety net, no automation), hooks blocking Claude unnecessarily (overly strict PreToolUse that never allows anything)
- 🟡 Warning — hooks not committed to repo (team doesn't benefit), synchronous hooks for slow operations (blocks Claude mid-task)
- 🟢 Suggestion — add auto-format hook (PostToolUse on Edit), add Stop hook enforcing tests pass, block common accidents (force push, DROP TABLE)
Anti-Patterns
- Hooks that always exit 1 (defeats the purpose — Claude can't do any work)
- Slow operations in synchronous hooks (use
async: true for anything taking >1s)
- Not committing hooks to repo (only you benefit, not the team)
- Complex hooks with no error handling (hooks that crash silently, allowing everything)
Integration
- Use with
claude-md-authoring (document hook behaviors in CLAUDE.md so team understands them)
- Use with
claude-project-settings (hooks configured in same settings.json)
- Agent:
@debugger can help debug why hooks are firing unexpectedly