Master Claude Code in a Week (Part 7): Hooks
This is the seventh part of the "Master Claude Code in a Week" series. In this article, we'll explore Hooks — event-driven automation for Claude Code.
What are Hooks?
Hooks are event-driven shell commands that automatically execute when Claude Code events occur. Hooks let you "hook into" the Claude Code lifecycle to perform actions like formatting, validation, and notification.
┌────────────────────────────────────────────────────────────────┐
│ HOOKS │
├────────────────────────────────────────────────────────────────┤
│ │
│ Claude Code Event │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PreToolUse │────►│ format-code.sh │ │
│ │ (Write file) │ │ (Auto-format) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Tool executes │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PostToolUse │────►│ security-scan.sh│ │
│ │ (Write done) │ │ (Scan for issues)│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Hook Types
Claude Code has 4 hook types with 25 events:
1. Tool Hooks
| Event | When | Use case |
|---|---|---|
PreToolUse |
Before tool runs | Validation, formatting |
PostToolUse |
After tool completes | Scanning, logging |
PostToolUseFailure |
When tool fails | Error logging, recovery |
PermissionRequest |
When permission needed | Custom permission logic |
2. Session Hooks
| Event | When | Use case |
|---|---|---|
SessionStart |
Session starts | Setup, initialization |
SessionEnd |
Session ends | Cleanup, summary |
Stop |
User stops | Save state |
StopFailure |
Stop fails | Recovery |
SubagentStart |
Subagent starts | Logging |
SubagentStop |
Subagent ends | Cleanup |
3. Task Hooks
| Event | When | Use case |
|---|---|---|
UserPromptSubmit |
User sends prompt | Validation, logging |
TaskCompleted |
Task completes | Notification |
TaskCreated |
New task created | Tracking |
TeammateIdle |
Teammate idle | Workload balance |
4. Lifecycle Hooks
| Event | When | Use case |
|---|---|---|
ConfigChange |
Config changes | Reload |
CwdChanged |
Directory changes | Context update |
FileChanged |
File changes | Auto-refresh |
PreCompact |
Before compact | Save important context |
PostCompact |
After compact | Restore context |
Configuring Hooks
settings.json Location
Global:
~/.claude/settings.json
Project:
.claude/settings.json
Configuration Structure
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
],
"SessionStart": [
{
"hooks": ["~/.claude/hooks/session-start.sh"]
}
],
"TaskCompleted": [
{
"hooks": ["~/.claude/hooks/notify-team.sh"]
}
]
}
}
Matcher Options
| Matcher | Description | Example |
|---|---|---|
"Write" |
File write operations | Format before write |
"Read" |
File read operations | Log file access |
"Bash" |
Shell commands | Validate commands |
"*" |
All tools | Global logging |
"Edit" |
File edits | Backup before edit |
Installing Hooks
Step 1: Create directory
mkdir -p ~/.claude/hooks
Step 2: Copy hooks from claude-howto
cp claude-howto/06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh
Step 3: Configure settings.json
# Create or edit settings.json
cat > ~/.claude/settings.json << 'EOF'
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
]
}
}
EOF
Example Hooks
1. Auto-format Code
File: ~/.claude/hooks/format-code.sh
#!/bin/bash
# Auto-format code before writing
FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*.php)
if command -v pint &> /dev/null; then
pint "$FILE_PATH" 2>/dev/null
fi
;;
*.js|*.ts|*.jsx|*.tsx)
if command -v prettier &> /dev/null; then
prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
*.py)
if command -v black &> /dev/null; then
black "$FILE_PATH" 2>/dev/null
fi
;;
esac
exit 0
2. Security Scan
File: ~/.claude/hooks/security-scan.sh
#!/bin/bash
# Scan for security issues after file write
FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"
ISSUES_FOUND=0
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi
# Check for hardcoded secrets
if grep -iE "(password|api_key|secret)\s*=\s*['\"]" "$FILE_PATH" > /dev/null 2>&1; then
echo "⚠️ Potential hardcoded secret found"
ISSUES_FOUND=1
fi
# Check for debug statements
if grep -E "(console\.log|var_dump|dd\()" "$FILE_PATH" > /dev/null 2>&1; then
echo "⚠️ Debug statement found"
ISSUES_FOUND=1
fi
if [ $ISSUES_FOUND -eq 1 ]; then
echo "Security scan found issues in: $FILE_PATH"
fi
exit 0
3. Log Bash Commands
File: ~/.claude/hooks/log-bash.sh
#!/bin/bash
# Log all bash commands executed by Claude
LOG_FILE="${HOME}/.claude/logs/bash-$(date +%Y-%m-%d).log"
mkdir -p "$(dirname "$LOG_FILE")"
COMMAND="$CLAUDE_TOOL_INPUT_COMMAND"
if [ -n "$COMMAND" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $COMMAND" >> "$LOG_FILE"
fi
exit 0
4. Team Notification
File: ~/.claude/hooks/notify-team.sh
#!/bin/bash
# Notify team when task is completed
WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
if [ -z "$WEBHOOK_URL" ]; then
exit 0
fi
TASK_NAME="${CLAUDE_TASK_NAME:-Unknown task}"
PROJECT_NAME="$(basename "$(pwd)")"
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"✅ Task completed in $PROJECT_NAME: $TASK_NAME\"
}" > /dev/null
exit 0
Environment Variables
Hooks receive information via environment variables:
Tool Hooks
| Variable | Description |
|---|---|
CLAUDE_TOOL_NAME |
Name of tool running |
CLAUDE_TOOL_INPUT_PATH |
File path (for file operations) |
CLAUDE_TOOL_INPUT_COMMAND |
Command (for bash) |
CLAUDE_TOOL_OUTPUT |
Tool output (PostToolUse) |
Session Hooks
| Variable | Description |
|---|---|
CLAUDE_SESSION_ID |
Session ID |
CLAUDE_SESSION_START |
Start timestamp |
CLAUDE_PROJECT_DIR |
Project directory |
Best Practices
1. Exit codes matter
# ✅ Good - Use exit codes properly
if [ -f "$FILE" ]; then
do_something
exit 0 # Success
else
echo "File not found"
exit 1 # Failure - may block operation
fi
2. Keep hooks fast
# ❌ Bad - Slow hook
npm install # Don't do heavy operations
# ✅ Good - Quick check
if [ ! -d "node_modules" ]; then
echo "⚠️ Dependencies not installed. Run: npm install"
fi
3. Fail gracefully
#!/bin/bash
# ✅ Good - Handle missing tools
if ! command -v prettier &> /dev/null; then
exit 0
fi
prettier --write "$FILE_PATH"
4. Use logging
#!/bin/bash
LOG_FILE="${HOME}/.claude/logs/hooks.log"
mkdir -p "$(dirname "$LOG_FILE")"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
log "Starting security scan for $FILE_PATH"
Complete Configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
},
{
"matcher": "Bash",
"hooks": ["~/.claude/hooks/log-bash.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
],
"TaskCompleted": [
{
"hooks": ["~/.claude/hooks/notify-team.sh"]
}
]
}
}
Summary
Hooks provide powerful automation:
- ✅ Event-driven execution
- ✅ 25 hook events
- ✅ Custom validation and formatting
- ✅ Integration with external tools
- ✅ Team notifications
Next Up
In the next part, we'll explore MCP Protocol — how to connect Claude Code with external tools and APIs.
References
This series is translated and expanded from claude-howto — MIT License.