Time: 1 hour | Complexity: ⭐⭐ Intermediate
Automate responses to system events. Create scripts that run before or after Claude Code operations.
- How hooks work and when they trigger
- Creating pre-commit validation
- Building post-action notifications
- Writing safe automation scripts
- Common hook patterns
A hook is a script that runs automatically in response to an event.
Before you commit changes:
You run: git commit -m "Fix bug"
↓
Hook runs: Check version number consistency
↓
If version wrong:
❌ Commit blocked
Error message shows what's wrong
↓
You fix: Update VERSION file
↓
Commit succeeds
Hooks prevent common mistakes from reaching git.
Hooks can trigger on:
| Event | Timing | Use Case |
|---|---|---|
| PreToolUse | Before Claude runs a tool | Validate request |
| PostToolUse | After Claude runs a tool | Log results, check output |
| PreCommit | Before git commit | Validate changes |
| PostPush | After git push | Notify team |
Hooks are bash (or PowerShell) scripts in .claude/hooks/.
#!/bin/bash
# Hook: validate-version
# Event: PreCommit
# Description: Check that VERSION file is updated with other changes
# Get the files being committed
FILES=$(git diff --cached --name-only)
# Check if guide files were changed
if echo "$FILES" | grep -q "guide/"; then
# If guide/ changed, VERSION must also be changed
if ! echo "$FILES" | grep -q "VERSION"; then
echo "❌ Error: guide/ was modified but VERSION wasn't updated"
echo "Run: echo '3.x.x' > VERSION"
exit 1 # Block commit
fi
fi
exit 0 # Allow commitmy-project/
└── .claude/
└── hooks/
├── validate-version.sh
└── notify-team.sh
exit 0 # Success - allow operation to proceed
exit 1 # Failure - block operation and show error
exit 2 # Warning - allow but show warning messageBlock commits that fail validation:
#!/bin/bash
# Hook: security-check.sh
# Block commits if security issues found
# Check for hardcoded API keys
if grep -r "sk_live_" .; then
echo "❌ ERROR: Found hardcoded Stripe key"
exit 1
fi
# Check for console.log in production code (not tests)
if grep -r "console.log" src/ --exclude-dir=tests; then
echo "❌ ERROR: Found console.log in source code"
exit 1
fi
# Check for TODO comments (warning, not block)
if grep -r "TODO:" src/; then
echo "⚠️ Warning: TODO comments found (not blocking)"
fi
exit 0After a commit succeeds:
#!/bin/bash
# Hook: notify-team.sh
# Notify team after certain commits
COMMIT_MSG=$(git log -1 --pretty=%B)
# If security-related commit
if echo "$COMMIT_MSG" | grep -i "security"; then
echo "🔐 Security commit: $COMMIT_MSG"
# Send to Slack (optional)
# curl -X POST $SLACK_WEBHOOK -d "Security update: $COMMIT_MSG"
fi
exit 0Warn if dependencies need updating:
#!/bin/bash
# Hook: check-deps.sh
# Check if package.json changed without updating lock file
FILES=$(git diff --cached --name-only)
if echo "$FILES" | grep -q "package.json"; then
if ! echo "$FILES" | grep -q "package-lock.json"; then
echo "⚠️ Warning: package.json changed but lock file wasn't updated"
echo "Run: npm install"
fi
fi
exit 0Hooks are registered in .claude/settings.json:
{
"hooks": {
"pre_commit": ["validate-version.sh", "security-check.sh"],
"post_commit": ["notify-team.sh"],
"post_push": ["deploy-staging.sh"]
}
}Or in settings.yaml:
hooks:
pre_commit:
- path: hooks/validate-version.sh
description: "Check VERSION file updated"
blocking: true
- path: hooks/security-check.sh
blocking: true
post_commit:
- path: hooks/notify-team.sh
blocking: false✅ Make hooks idempotent (safe to run multiple times)
✅ Log what the hook is doing
✅ Exit with clear error messages
✅ Use set -e at top to fail on first error
✅ Make hooks executable: chmod +x hook.sh
❌ Make hooks take >5 seconds (blocks workflow) ❌ Have hooks make network calls (unreliable) ❌ Have hooks modify files (they validate only) ❌ Make hooks too strict (frustrate developers) ❌ Forget to test hooks locally first
#!/bin/bash
set -euo pipefail
# Hook template for safe, clear automation
HOOK_NAME="my-hook"
HOOK_VERSION="1.0.0"
# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
log_error() {
echo -e "${RED}❌ $1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_success() {
echo -e "${GREEN}✅ $1${NC}"
}
# Main validation logic
main() {
echo "Running: $HOOK_NAME ($HOOK_VERSION)"
# Your checks here
if some_check_fails; then
log_error "Check failed because X"
return 1
fi
log_success "All checks passed"
return 0
}
# Run and exit
main
exit $?You want to prevent accidental commits with:
- Trailing whitespace
- Missing test files for new code
- Unresolved merge conflicts
cat > .claude/hooks/pre-commit-validation.sh << 'EOF'
#!/bin/bash
set -euo pipefail
echo "🔍 Running pre-commit validation..."
# Check 1: No trailing whitespace
if git diff --cached | grep -E '^[+].*\s+$' > /dev/null; then
echo "❌ Trailing whitespace found:"
git diff --cached | grep -E '^[+].*\s+$'
exit 1
fi
# Check 2: No merge conflict markers
if git diff --cached | grep -E '^[+].*<<<<<<|^[+].*======|^[+].*>>>>>>' > /dev/null; then
echo "❌ Merge conflict markers found"
exit 1
fi
# Check 3: New files should have tests
STAGED_FILES=$(git diff --cached --name-only)
for file in $STAGED_FILES; do
if [[ $file == src/*.ts && $file != *test* ]]; then
TEST_FILE="${file%.ts}.test.ts"
if ! git ls-files | grep -q "$TEST_FILE"; then
echo "⚠️ Warning: New file $file has no test file"
fi
fi
done
echo "✅ Pre-commit validation passed"
exit 0
EOF
chmod +x .claude/hooks/pre-commit-validation.sh{
"hooks": {
"pre_commit": ["hooks/pre-commit-validation.sh"]
}
}Make a file with trailing whitespace:
echo "test line " > test.txt # Note the trailing spaces
git add test.txtTry to commit:
git commit -m "Test hook"The hook blocks:
❌ Trailing whitespace found:
+test line
echo "test line" > test.txt # Remove trailing spaces
git add test.txt
git commit -m "Test hook (fixed)"Now it succeeds:
✅ Pre-commit validation passed
If a hook fails mysteriously:
- Run manually:
bash .claude/hooks/my-hook.sh- Add debug output:
set -x # Print every command- Check exit code:
bash .claude/hooks/my-hook.sh; echo "Exit: $?"- Test hook conditions:
# Test if a file was changed
git diff --cached --name-only | grep "VERSION"
echo $? # 0 = found, 1 = not found✓ You've created at least one hook script
✓ You understand hook event types (pre-commit, post-commit, etc.)
✓ You can register hooks in settings.json or settings.yaml
✓ You've tested a hook locally
✓ You know what exit codes mean (0 = success, 1 = failure)
Module 07: Advanced Patterns covers:
- Multi-agent orchestration
- Building complex workflows
- Error handling and recovery
- Production-grade automation
- Team coordination patterns
This teaches you how to combine all previous concepts into sophisticated multi-agent systems.
Completed Module 06? → Ready for Module 07: Advanced Patterns