7.3 KiB
7.3 KiB
name, description
| name | description |
|---|---|
| Claude Code Hooks | Configure event-driven hooks for Claude Code to automate workflows, validate code, inject context, and control tool execution. Use PROACTIVELY when users want automation that runs automatically (not manually), mention "automatically run", "on save", "after editing", "event-driven", "workflow automation", or "enforce rules". NOT for one-time scripts. |
Claude Code Hooks
When to Use This Skill
Use this skill when:
- Automating workflows with event triggers
- Enforcing code standards or policies
- Validating changes before/after tool execution
- Injecting context at session start
- Logging or monitoring tool usage
- Setting up team-wide automation
Do NOT use this skill for:
- Creating slash commands (use claude-commands skill)
- Building plugins (use claude-plugins skill)
- One-time scripts (run them directly)
Quick Start
Configuration File Locations
.claude/settings.json # Project (team)
.claude/settings.local.json # Project (personal)
~/.claude/settings.json # Global (personal)
Simple Example - Auto-Format
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS && exit 0"
}]
}]
}
}
Hook Types
| Event | When | Common Use |
|---|---|---|
| SessionStart | Session begins | Inject context |
| SessionEnd | Session ends | Cleanup, backups |
| PreToolUse | Before tool execution | Validation, checks |
| PostToolUse | After tool completes | Formatting, linting |
| UserPromptSubmit | User submits prompt | Logging |
| Notification | Claude sends notification | Desktop alerts |
| Stop | Agent finishes | Post-processing |
Core Concepts
Matchers
// Single tool
"matcher": "Write"
// Multiple (OR)
"matcher": "Write|Edit|Read"
// All tools
"matcher": "*"
// Git operations
"matcher": "Bash(git:*)"
Exit Codes
exit 0 # Success - Continue
exit 1 # Non-blocking error - Continue
exit 2 # Blocking error - Stop operation
Environment Variables
$CLAUDE_PROJECT_DIR # Current project
$TOOL_NAME # Tool being used
$TOOL_ARGS # Tool arguments
$HOOK_EVENT # Event type
Common Use Cases
Auto-Format on Save
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && npx prettier --write $TOOL_ARGS && exit 0"
}]
}]
}
}
Security Validation
{
"hooks": {
"PreToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "if grep -qiE '(password|api[_-]?key|secret)' $TOOL_ARGS 2>/dev/null; then echo 'Error: Possible secret' >&2; exit 2; fi; exit 0",
"timeout": 30
}]
}]
}
}
Test Before Commit
{
"hooks": {
"PreToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && npm test -- --silent || (echo 'Tests failed' >&2; exit 2)",
"timeout": 120
}]
}]
}
}
For more examples, see examples.md
Hook Configuration
Basic Structure
{
"type": "command",
"command": "your-command-here",
"timeout": 60 // Optional, default 60 seconds
}
Multiple Hooks (Sequential)
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": "npx prettier --write $TOOL_ARGS"},
{"type": "command", "command": "npx eslint --fix $TOOL_ARGS"},
{"type": "command", "command": "git add $TOOL_ARGS"}
]
}]
}
}
Conditional Execution
{
"type": "command",
"command": "if [[ $TOOL_ARGS == *.js ]]; then npm run format $TOOL_ARGS; fi; exit 0"
}
Settings Hierarchy
Load Order (highest to lowest priority):
.claude/settings.local.json- Project, personal (gitignored).claude/settings.json- Project, team (in git)~/.claude/settings.json- Global, personal
Use Cases:
- Global: Personal preferences, universal logging
- Project: Team standards, project automation
- Local: Personal overrides, experiments
Testing Hooks
Start Simple
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "echo 'Hook triggered: $TOOL_ARGS' && exit 0"
}]
}]
}
}
Test Exit Codes
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "echo 'This blocks' >&2 && exit 2"
}]
}]
}
}
Try writing - should be blocked.
Best Practices
1. Always Exit Explicitly
# ✓ Good
command && exit 0
# ✗ Bad
command
2. Use Timeouts
{
"command": "npm test",
"timeout": 120 // Don't hang forever
}
3. Handle Errors Gracefully
if [ -f "$TOOL_ARGS" ]; then
process_file "$TOOL_ARGS"
else
echo "File not found" >&2
fi
exit 0 # Don't block on missing file
4. Use Scripts for Complex Logic
// ✓ Good - external script
{
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check.sh $TOOL_ARGS"
}
// ✗ Bad - complex bash in JSON
{
"command": "if [ -f $TOOL_ARGS ]; then cat $TOOL_ARGS | grep pattern | wc -l; fi"
}
5. Test Before Team Deployment
Test in .claude/settings.local.json before adding to team settings.
Troubleshooting
| Problem | Solution |
|---|---|
| Not triggering | Check matcher, restart Claude |
| Fails silently | Check exit codes (use 0 for success) |
| Command not found | Use full path |
| Permission denied | chmod +x script.sh |
| Timeout | Increase timeout or optimize |
For detailed troubleshooting, see troubleshooting.md
Security
Validate Input
# Sanitize TOOL_ARGS
if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
echo "Invalid path" >&2
exit 2
fi
Limit Permissions
// ✓ Specific
"matcher": "Write|Edit"
// ✗ Too broad
"matcher": "*"
Avoid Destructive Commands
# ✗ Dangerous
rm -rf $TOOL_ARGS
# ✓ Safer
if [[ -f "$TOOL_ARGS" ]] && [[ "$TOOL_ARGS" != "/" ]]; then
rm "$TOOL_ARGS"
fi
Quick Reference
Common Patterns
// Format on save
"PostToolUse": [{"matcher": "Write|Edit", "hooks": [{"type": "command", "command": "prettier --write $TOOL_ARGS"}]}]
// Block secrets
"PreToolUse": [{"matcher": "Write", "hooks": [{"type": "command", "command": "grep -q 'secret' $TOOL_ARGS && exit 2 || exit 0"}]}]
// Log activity
"PreToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "echo \"$TOOL_NAME: $TOOL_ARGS\" >> .claude/log && exit 0"}]}]
Additional Resources
Need more?
- Complete Examples - Working hook configurations
- Advanced Patterns - Complex workflows
- Troubleshooting Guide - Problem solutions
- Official Docs
💡 Tip: Start simple, test thoroughly, use exit codes correctly. Hooks are powerful - use them wisely.
Remember: Hooks automate workflows. Exit codes control flow. Test in local settings first. Use specific matchers for security.