Files
claude-plugins/claude-code/skills/claude-hooks/SKILL.md

405 lines
8.0 KiB
Markdown
Raw Normal View History

2025-10-28 12:28:48 -05:00
---
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 setting up automation, enforcing standards, integrating external tools, or when users mention "automatically run", "on save", "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-driven triggers
- Enforcing code standards or security 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-command-expert skill)
- Building full plugins (use claude-plugin skill)
- One-time scripts (just run them directly)
## Quick Start
### 1. Hook Configuration File
```bash
# Project-wide (team shared)
.claude/settings.json
# User-specific (not shared)
.claude/settings.local.json
# Global (all projects)
~/.claude/settings.json
```
### 2. Simple Example - Log File Changes
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "echo \"$(date): Modified $TOOL_ARGS\" >> .claude/changes.log"
}
]
}
]
}
}
```
## Hook Types
| Event | When It Runs | Common Use |
|-------|--------------|-----------|
| SessionStart | Session begins | Inject project context |
| SessionEnd | Session ends | Cleanup, backups |
| PreToolUse | Before tool execution | Validation, permission checks |
| PostToolUse | After tool completes | Formatting, linting |
| UserPromptSubmit | User submits prompt | Logging, analytics |
| Notification | Claude sends notification | Desktop alerts |
| Stop | Agent finishes responding | Post-response processing |
## Core Concepts
### Matcher Patterns
```json
// Single tool
"matcher": "Write"
// Multiple tools (OR)
"matcher": "Write|Edit|Read"
// All tools
"matcher": "*"
// Git operations
"matcher": "Bash(git:*)"
```
### Exit Codes
```bash
# Success - Continue
exit 0
# Blocking Error - Stop operation
exit 2
# Non-Blocking Error - Continue
exit 1
```
### Environment Variables
```bash
$CLAUDE_PROJECT_DIR # Current project directory
$TOOL_NAME # Name of the tool being used
$TOOL_ARGS # Arguments passed to the tool
$HOOK_EVENT # Event type (PreToolUse, etc.)
```
## 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 detected' >&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 complete 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
Run several hooks in sequence:
```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 File Hierarchy
**Load Order** (highest to lowest priority):
1. `.claude/settings.local.json` - Project, user-only (gitignored)
2. `.claude/settings.json` - Project, team-shared (in git)
3. `~/.claude/settings.json` - Global, user-only
**Use Cases**:
- **Global**: Personal preferences, universal logging
- **Project**: Team standards, project automation
- **Local**: Personal overrides, development experiments
## Testing Hooks
### Start Simple
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'Hook triggered for: $TOOL_ARGS' && exit 0"
}
]
}
]
}
}
```
### Test Exit Codes
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'This should block' >&2 && exit 2"
}
]
}
]
}
}
```
Try writing a file - should be blocked.
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Hook not triggering | Check matcher pattern, restart Claude Code |
| Hook fails silently | Check exit codes (use 0 for success) |
| Command not found | Use full path: `/usr/local/bin/prettier` |
| Permission denied | `chmod +x .claude/hooks/script.sh` |
| Timeout | Increase timeout value or optimize command |
For detailed troubleshooting, see [troubleshooting.md](troubleshooting.md)
## Best Practices
### 1. Always Exit Explicitly
```bash
# Good
command && exit 0
# Bad
command
```
### 2. Use Timeouts
```json
{
"command": "npm test",
"timeout": 120 // Don't let tests run 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
// Bad - complex bash in JSON
{
"command": "if [ -f $TOOL_ARGS ]; then cat $TOOL_ARGS | grep pattern | wc -l; fi"
}
// Good - external script
{
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-file.sh $TOOL_ARGS"
}
```
### 5. Test Before Team Deployment
Test in `.claude/settings.local.json` before adding to `.claude/settings.json`.
## Security Considerations
### Validate Input
```bash
# Sanitize TOOL_ARGS
if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
echo "Invalid file path" >&2
exit 2
fi
```
### Limit Permissions
```json
// Specific (good)
"matcher": "Write|Edit"
// Too broad (risky)
"matcher": "*"
```
### Avoid Destructive Commands
```bash
# Dangerous
rm -rf $TOOL_ARGS
# Safer
if [[ -f "$TOOL_ARGS" ]] && [[ "$TOOL_ARGS" != "/" ]]; then
rm "$TOOL_ARGS"
fi
```
## Resources
- [Complete Examples](examples.md) - Working hook configurations
- [Advanced Patterns](patterns.md) - Complex workflows
- [Troubleshooting Guide](troubleshooting.md) - Problem-solution reference
- [Official Documentation](https://docs.claude.com/en/docs/claude-code/hooks)
## Quick Reference Card
### 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 all activity
"PreToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "echo \"$TOOL_NAME: $TOOL_ARGS\" >> .claude/log && exit 0"}]}]
```
---
**Remember**: Hooks are powerful automation tools. Start simple, test thoroughly, use exit codes properly to control flow.