| name | creating-hooks |
| description | Creates Claude Code hooks for automation and workflow customization. Guides through hook events, configuration, and script creation. Use when user wants to create a hook, automate Claude Code, or asks about hook events. |
Creating Hooks
Guides creation of Claude Code hooks for automation and workflow customization.
Quick Start
- Choose hook event (when should it trigger?)
- Configure in settings.json
- Create hook script
- Test the hook
Workflow: Create New Hook
Progress:
- [ ] Select hook event
- [ ] Add to settings.json
- [ ] Create hook script
- [ ] Test and validate
Step 1: Select Hook Event
| Event | When It Triggers | Common Use |
|---|---|---|
PreToolUse |
Before tool runs | Block/modify tools |
PostToolUse |
After tool succeeds | Validate, log, feedback |
UserPromptSubmit |
User sends message | Inject context, validate |
SessionStart |
Session begins | Load context, init state |
SessionEnd |
Session ends | Cleanup, save state |
Stop |
Agent finishes | Decide if should continue |
Full event reference: reference.md
Step 2: Configure settings.json
Location priority (highest wins):
.claude/settings.local.json(local, not committed).claude/settings.json(project)~/.claude/settings.json(user)
Basic structure:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh\""
}
]
}
]
}
}
Step 3: Create Hook Script
Use templates from templates/ directory.
Key requirements:
- Read JSON from stdin
- Use exit codes for control (0=success, 2=block)
- Output JSON for decisions
Step 4: Test
Run hook manually with test input:
echo '{"tool_name":"Write"}' | bash .claude/hooks/my-hook.sh
Hook Configuration
Matcher Patterns
"matcher": "Write" // Exact match
"matcher": "Edit|Write" // Multiple tools
"matcher": "mcp__.*" // MCP tools (regex)
"matcher": "*" // All tools
Matchers apply to: PreToolUse, PostToolUse, PermissionRequest
Timeout
{
"type": "command",
"command": "...",
"timeout": 120
}
Default: 60 seconds. Max recommended: 300 seconds.
Exit Codes
| Code | Meaning | Behavior |
|---|---|---|
| 0 | Success | Continue normally |
| 2 | Block | Stop action, show error |
| Other | Non-blocking error | Log only (verbose mode) |
JSON Output
Return JSON to stdout for decisions:
{
"decision": "block",
"reason": "Why blocked",
"additionalContext": "Info for Claude"
}
Decision values by event:
PreToolUse:allow,deny,askPostToolUse:block(with reason)UserPromptSubmit:block(with reason)Stop:block(requires reason)
Security Best Practices
- Quote all variables:
"$VAR"not$VAR - Use absolute paths:
"$CLAUDE_PROJECT_DIR/..." - Validate inputs: Check before processing
- Block path traversal: Reject paths with
.. - Set timeouts: Prevent runaway scripts
Environment Variables
Available in all hooks:
CLAUDE_PROJECT_DIR- Project root pathCLAUDE_CODE_REMOTE- "true" if web environment
SessionStart only:
CLAUDE_ENV_FILE- Path to persist env vars
Common Patterns
Inject Context on Session Start
#!/bin/bash
# Output context for Claude
echo '{"additionalContext": "Project uses TypeScript"}'
exit 0
Block Dangerous File Edits
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *".env"* ]]; then
echo "Blocking edit to sensitive file" >&2
exit 2
fi
exit 0
Log All Tool Usage
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
echo "$(date -Iseconds) $TOOL" >> "$CLAUDE_PROJECT_DIR/.claude/tool.log"
exit 0
See reference.md for complete event details and more examples.