2025-10-28 12:28:48 -05:00
---
name: Claude Code Hooks
2025-10-28 12:48:17 -05:00
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.
2025-10-28 12:28:48 -05:00
---
# Claude Code Hooks
## When to Use This Skill
Use this skill when:
2025-10-28 12:48:17 -05:00
- Automating workflows with event triggers
- Enforcing code standards or policies
2025-10-28 12:28:48 -05:00
- 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:
2025-10-28 12:48:17 -05:00
- Creating slash commands (use claude-commands skill)
- Building plugins (use claude-plugins skill)
- One-time scripts (run them directly)
2025-10-28 12:28:48 -05:00
## Quick Start
2025-10-28 12:48:17 -05:00
### Configuration File Locations
2025-10-28 12:28:48 -05:00
```bash
2025-10-28 12:48:17 -05:00
.claude/settings.json # Project (team)
.claude/settings.local.json # Project (personal)
~/.claude/settings.json # Global (personal)
2025-10-28 12:28:48 -05:00
```
2025-10-28 12:48:17 -05:00
### Simple Example - Auto-Format
2025-10-28 12:28:48 -05:00
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS & & exit 0"
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
## Hook Types
2025-10-28 12:48:17 -05:00
| Event | When | Common Use |
|-------|------|-----------|
| SessionStart | Session begins | Inject context |
2025-10-28 12:28:48 -05:00
| SessionEnd | Session ends | Cleanup, backups |
2025-10-28 12:48:17 -05:00
| PreToolUse | Before tool execution | Validation, checks |
2025-10-28 12:28:48 -05:00
| PostToolUse | After tool completes | Formatting, linting |
2025-10-28 12:48:17 -05:00
| UserPromptSubmit | User submits prompt | Logging |
2025-10-28 12:28:48 -05:00
| Notification | Claude sends notification | Desktop alerts |
2025-10-28 12:48:17 -05:00
| Stop | Agent finishes | Post-processing |
2025-10-28 12:28:48 -05:00
## Core Concepts
2025-10-28 12:48:17 -05:00
### Matchers
2025-10-28 12:28:48 -05:00
```json
// Single tool
"matcher": "Write"
2025-10-28 12:48:17 -05:00
// Multiple (OR)
2025-10-28 12:28:48 -05:00
"matcher": "Write|Edit|Read"
// All tools
"matcher": "*"
// Git operations
"matcher": "Bash(git:*)"
```
### Exit Codes
```bash
2025-10-28 12:48:17 -05:00
exit 0 # Success - Continue
exit 1 # Non-blocking error - Continue
exit 2 # Blocking error - Stop operation
2025-10-28 12:28:48 -05:00
```
### Environment Variables
```bash
2025-10-28 12:48:17 -05:00
$CLAUDE_PROJECT_DIR # Current project
$TOOL_NAME # Tool being used
$TOOL_ARGS # Tool arguments
$HOOK_EVENT # Event type
2025-10-28 12:28:48 -05:00
```
## Common Use Cases
### Auto-Format on Save
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR & & npx prettier --write $TOOL_ARGS & & exit 0"
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
### Security Validation
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"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
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
### Test Before Commit
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"PreToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR & & npm test -- --silent || (echo 'Tests failed' >&2; exit 2)",
"timeout": 120
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
2025-10-28 12:48:17 -05:00
For more examples, see [examples.md ](examples.md )
2025-10-28 12:28:48 -05:00
## Hook Configuration
### Basic Structure
```json
{
"type": "command",
"command": "your-command-here",
"timeout": 60 // Optional, default 60 seconds
}
```
2025-10-28 12:48:17 -05:00
### Multiple Hooks (Sequential)
2025-10-28 12:28:48 -05:00
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"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"}
]
}]
2025-10-28 12:28:48 -05:00
}
}
```
### Conditional Execution
```json
{
"type": "command",
"command": "if [[ $TOOL_ARGS == *.js ]]; then npm run format $TOOL_ARGS; fi; exit 0"
}
```
2025-10-28 12:48:17 -05:00
## Settings Hierarchy
2025-10-28 12:28:48 -05:00
**Load Order** (highest to lowest priority):
2025-10-28 12:48:17 -05:00
1. `.claude/settings.local.json` - Project, personal (gitignored)
2. `.claude/settings.json` - Project, team (in git)
3. `~/.claude/settings.json` - Global, personal
2025-10-28 12:28:48 -05:00
**Use Cases**:
- **Global**: Personal preferences, universal logging
- **Project**: Team standards, project automation
2025-10-28 12:48:17 -05:00
- **Local**: Personal overrides, experiments
2025-10-28 12:28:48 -05:00
## Testing Hooks
### Start Simple
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "echo 'Hook triggered: $TOOL_ARGS' & & exit 0"
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
### Test Exit Codes
```json
{
"hooks": {
2025-10-28 12:48:17 -05:00
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "echo 'This blocks' >& 2 & & exit 2"
}]
}]
2025-10-28 12:28:48 -05:00
}
}
```
2025-10-28 12:48:17 -05:00
Try writing - should be blocked.
2025-10-28 12:28:48 -05:00
## Best Practices
### 1. Always Exit Explicitly
```bash
2025-10-28 12:48:17 -05:00
# ✓ Good
2025-10-28 12:28:48 -05:00
command & & exit 0
2025-10-28 12:48:17 -05:00
# ✗ Bad
2025-10-28 12:28:48 -05:00
command
```
### 2. Use Timeouts
```json
{
"command": "npm test",
2025-10-28 12:48:17 -05:00
"timeout": 120 // Don't hang forever
2025-10-28 12:28:48 -05:00
}
```
### 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
2025-10-28 12:48:17 -05:00
// ✓ Good - external script
2025-10-28 12:28:48 -05:00
{
2025-10-28 12:48:17 -05:00
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check.sh $TOOL_ARGS"
2025-10-28 12:28:48 -05:00
}
2025-10-28 12:48:17 -05:00
// ✗ Bad - complex bash in JSON
2025-10-28 12:28:48 -05:00
{
2025-10-28 12:48:17 -05:00
"command": "if [ -f $TOOL_ARGS ]; then cat $TOOL_ARGS | grep pattern | wc -l; fi"
2025-10-28 12:28:48 -05:00
}
```
### 5. Test Before Team Deployment
2025-10-28 12:48:17 -05:00
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 |
2025-10-28 12:28:48 -05:00
2025-10-28 12:48:17 -05:00
For detailed troubleshooting, see [troubleshooting.md ](troubleshooting.md )
## Security
2025-10-28 12:28:48 -05:00
### Validate Input
```bash
# Sanitize TOOL_ARGS
if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
2025-10-28 12:48:17 -05:00
echo "Invalid path" >& 2
2025-10-28 12:28:48 -05:00
exit 2
fi
```
### Limit Permissions
```json
2025-10-28 12:48:17 -05:00
// ✓ Specific
2025-10-28 12:28:48 -05:00
"matcher": "Write|Edit"
2025-10-28 12:48:17 -05:00
// ✗ Too broad
2025-10-28 12:28:48 -05:00
"matcher": "*"
```
### Avoid Destructive Commands
```bash
2025-10-28 12:48:17 -05:00
# ✗ Dangerous
2025-10-28 12:28:48 -05:00
rm -rf $TOOL_ARGS
2025-10-28 12:48:17 -05:00
# ✓ Safer
2025-10-28 12:28:48 -05:00
if [[ -f "$TOOL_ARGS" ]] & & [[ "$TOOL_ARGS" != "/" ]]; then
rm "$TOOL_ARGS"
fi
```
2025-10-28 12:48:17 -05:00
## Quick Reference
2025-10-28 12:28:48 -05:00
### 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"}]}]
2025-10-28 12:48:17 -05:00
// Log activity
2025-10-28 12:28:48 -05:00
"PreToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "echo \"$TOOL_NAME: $TOOL_ARGS\" >> .claude/log && exit 0"}]}]
```
2025-10-28 12:48:17 -05:00
## 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.
2025-10-28 12:28:48 -05:00
---
2025-10-28 12:48:17 -05:00
**Remember**: Hooks automate workflows. Exit codes control flow. Test in local settings first. Use specific matchers for security.