Skip to content

Latest commit

 

History

History
435 lines (315 loc) · 8.12 KB

File metadata and controls

435 lines (315 loc) · 8.12 KB

Module 06: Hooks & Events

Time: 1 hour | Complexity: ⭐⭐ Intermediate

Goal

Automate responses to system events. Create scripts that run before or after Claude Code operations.


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?

A hook is a script that runs automatically in response to an event.

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 succeeds

Hooks prevent common mistakes from reaching git.

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

Hooks are bash (or PowerShell) scripts in .claude/hooks/.

Basic Hook Structure

#!/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 commit

File Location

my-project/
└── .claude/
    └── hooks/
        ├── validate-version.sh
        └── notify-team.sh

Hook Exit Codes

exit 0   # Success - allow operation to proceed
exit 1   # Failure - block operation and show error
exit 2   # Warning - allow but show warning message

Hook Patterns

Pattern 1: Pre-Commit Validation

Block 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 0

Pattern 2: Post-Commit Notification

After 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 0

Pattern 3: Dependency Check

Warn 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 0

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: false

Best Practices for Safe Hooks

DO

✅ 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

DON'T

❌ 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

#!/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 $?

Exercise: Create a Validation Hook

Scenario

You want to prevent accidental commits with:

  • Trailing whitespace
  • Missing test files for new code
  • Unresolved merge conflicts

Step 1: Create the Hook

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

Step 2: Register in settings.json

{
  "hooks": {
    "pre_commit": ["hooks/pre-commit-validation.sh"]
  }
}

Step 3: Test It

Make a file with trailing whitespace:

echo "test line   " > test.txt  # Note the trailing spaces
git add test.txt

Try to commit:

git commit -m "Test hook"

The hook blocks:

❌ Trailing whitespace found:
+test line

Step 4: Fix and Retry

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

Debugging Hooks

If a hook fails mysteriously:

  1. Run manually:
bash .claude/hooks/my-hook.sh
  1. Add debug output:
set -x  # Print every command
  1. Check exit code:
bash .claude/hooks/my-hook.sh; echo "Exit: $?"
  1. Test hook conditions:
# Test if a file was changed
git diff --cached --name-only | grep "VERSION"
echo $?  # 0 = found, 1 = not found

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?

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