--- name: Claude Code Hooks description: 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 ```bash .claude/settings.json # Project (team) .claude/settings.local.json # Project (personal) ~/.claude/settings.json # Global (personal) ``` ### Simple Example - Auto-Format ```json { "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 ```json // Single tool "matcher": "Write" // Multiple (OR) "matcher": "Write|Edit|Read" // All tools "matcher": "*" // Git operations "matcher": "Bash(git:*)" ``` ### Exit Codes ```bash exit 0 # Success - Continue exit 1 # Non-blocking error - Continue exit 2 # Blocking error - Stop operation ``` ### Environment Variables ```bash $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 ```json { "hooks": { "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "cd $CLAUDE_PROJECT_DIR && npx prettier --write $TOOL_ARGS && exit 0" }] }] } } ``` ### Security Validation ```json { "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 ```json { "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](examples.md) ## Hook Configuration ### Basic Structure ```json { "type": "command", "command": "your-command-here", "timeout": 60 // Optional, default 60 seconds } ``` ### Multiple Hooks (Sequential) ```json { "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 ```json { "type": "command", "command": "if [[ $TOOL_ARGS == *.js ]]; then npm run format $TOOL_ARGS; fi; exit 0" } ``` ## Settings Hierarchy **Load Order** (highest to lowest priority): 1. `.claude/settings.local.json` - Project, personal (gitignored) 2. `.claude/settings.json` - Project, team (in git) 3. `~/.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 ```json { "hooks": { "PostToolUse": [{ "matcher": "Write", "hooks": [{ "type": "command", "command": "echo 'Hook triggered: $TOOL_ARGS' && exit 0" }] }] } } ``` ### Test Exit Codes ```json { "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 ```bash # ✓ Good command && exit 0 # ✗ Bad command ``` ### 2. Use Timeouts ```json { "command": "npm test", "timeout": 120 // Don't hang forever } ``` ### 3. Handle Errors Gracefully ```bash 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 ```json // ✓ 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](troubleshooting.md) ## Security ### Validate Input ```bash # Sanitize TOOL_ARGS if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then echo "Invalid path" >&2 exit 2 fi ``` ### Limit Permissions ```json // ✓ Specific "matcher": "Write|Edit" // ✗ Too broad "matcher": "*" ``` ### Avoid Destructive Commands ```bash # ✗ Dangerous rm -rf $TOOL_ARGS # ✓ Safer if [[ -f "$TOOL_ARGS" ]] && [[ "$TOOL_ARGS" != "/" ]]; then rm "$TOOL_ARGS" fi ``` ## Quick Reference ### Common Patterns ```json // 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](examples.md) - Working hook configurations - [Advanced Patterns](patterns.md) - Complex workflows - [Troubleshooting Guide](troubleshooting.md) - Problem solutions - [Official Docs](https://docs.claude.com/en/docs/claude-code/hooks) 💡 **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.