Module 06: Hooks & Events
Module 06: Hooks & Events
Section titled “Module 06: Hooks & Events”Time: 1 hour | Complexity: ⭐⭐ Intermediate
Automate responses to system events. Create scripts that run before or after Claude Code operations.
What You’ll Learn
Section titled “What You’ll Learn”- How hooks work and when they trigger
- Creating pre-commit validation
- Building post-action notifications
- Writing safe automation scripts
- Common hook patterns
What Are Hooks?
Section titled “What Are Hooks?”A hook is a script that runs automatically in response to an event.
Example: Pre-Commit Hook
Section titled “Example: Pre-Commit Hook”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 succeedsHooks prevent common mistakes from reaching git.
Hook Events
Section titled “Hook Events”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 |
Creating Your First Hook
Section titled “Creating Your First Hook”Hooks are bash (or PowerShell) scripts in .claude/hooks/.
Basic Hook Structure
Section titled “Basic Hook Structure”#!/bin/bash
# Hook: validate-version# Event: PreCommit# Description: Check that VERSION file is updated with other changes
# Get the files being committedFILES=$(git diff --cached --name-only)
# Check if guide files were changedif 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 fifi
exit 0 # Allow commitFile Location
Section titled “File Location”my-project/└── .claude/ └── hooks/ ├── validate-version.sh └── notify-team.shHook Exit Codes
Section titled “Hook Exit Codes”exit 0 # Success - allow operation to proceedexit 1 # Failure - block operation and show errorexit 2 # Warning - allow but show warning messageHook Patterns
Section titled “Hook Patterns”Pattern 1: Pre-Commit Validation
Section titled “Pattern 1: Pre-Commit Validation”Block commits that fail validation:
#!/bin/bash# Block commits if security issues found
# Check for hardcoded API keysif grep -r "sk_live_" .; then echo "❌ ERROR: Found hardcoded Stripe key" exit 1fi
# 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 1fi
# Check for TODO comments (warning, not block)if grep -r "TODO:" src/; then echo "⚠️ Warning: TODO comments found (not blocking)"fi
exit 0Pattern 2: Post-Commit Notification
Section titled “Pattern 2: Post-Commit Notification”After a commit succeeds:
#!/bin/bash# Notify team after certain commits
COMMIT_MSG=$(git log -1 --pretty=%B)
# If security-related commitif 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 0Pattern 3: Dependency Check
Section titled “Pattern 3: Dependency Check”Warn if dependencies need updating:
#!/bin/bash# 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" fifi
exit 0Registering Hooks
Section titled “Registering Hooks”Hooks 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: falseBest Practices for Safe Hooks
Section titled “Best Practices for Safe Hooks”✅ 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
Safe Hook Template
Section titled “Safe Hook Template”#!/bin/bashset -euo pipefail
# Hook template for safe, clear automation
HOOK_NAME="my-hook"HOOK_VERSION="1.0.0"
# Colors for outputRED='\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 logicmain() { 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 exitmainexit $?Exercise: Create a Validation Hook
Section titled “Exercise: Create a Validation Hook”Scenario
Section titled “Scenario”You want to prevent accidental commits with:
- Trailing whitespace
- Missing test files for new code
- Unresolved merge conflicts
Step 1: Create the Hook
Section titled “Step 1: Create the Hook”cat > .claude/hooks/pre-commit-validation.sh << 'EOF'#!/bin/bashset -euo pipefail
echo "🔍 Running pre-commit validation..."
# Check 1: No trailing whitespaceif git diff --cached | grep -E '^[+].*\s+$' > /dev/null; then echo "❌ Trailing whitespace found:" git diff --cached | grep -E '^[+].*\s+$' exit 1fi
# Check 2: No merge conflict markersif git diff --cached | grep -E '^[+].*<<<<<<|^[+].*======|^[+].*>>>>>>' > /dev/null; then echo "❌ Merge conflict markers found" exit 1fi
# Check 3: New files should have testsSTAGED_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 fidone
echo "✅ Pre-commit validation passed"exit 0EOF
chmod +x .claude/hooks/pre-commit-validation.shStep 2: Register in settings.json
Section titled “Step 2: Register in settings.json”{ "hooks": { "pre_commit": ["hooks/pre-commit-validation.sh"] }}Step 3: Test It
Section titled “Step 3: Test It”Make a file with trailing whitespace:
echo "test line " > test.txt # Note the trailing spacesgit add test.txtTry to commit:
git commit -m "Test hook"The hook blocks:
❌ Trailing whitespace found:+test lineStep 4: Fix and Retry
Section titled “Step 4: Fix and Retry”echo "test line" > test.txt # Remove trailing spacesgit add test.txtgit commit -m "Test hook (fixed)"Now it succeeds:
✅ Pre-commit validation passedDebugging Hooks
Section titled “Debugging Hooks”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 changedgit diff --cached --name-only | grep "VERSION"echo $? # 0 = found, 1 = not foundValidation: You’re Ready If…
Section titled “Validation: You’re Ready If…”✓ 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)
What’s Next?
Section titled “What’s Next?”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