claude-code hooks exercises solutions examples hands-on
Workshop Exercise Solutions
Complete solutions for all workshop exercises with detailed explanations.
Beginner Exercise Solutions
Exercise 1: Time-stamped Notifications ⭐
Problem: Modify the notification to show the current time.
Solution:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Task completed at '$(date +\"%H:%M:%S\")'\" with title \"Claude Code\"'"
}
]
}
]
}
}Explanation: We use command substitution $(date +"%H:%M:%S") to insert the current time into the notification message.
Exercise 2: Timestamped Backups ⭐⭐
Problem: Create timestamped backups instead of simple .backup files.
Solution:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "for file in $CLAUDE_FILE_PATHS; do cp \"$file\" \"$file.backup.$(date +%Y%m%d_%H%M%S)\"; done"
}
]
}
]
}
}Explanation: The date +%Y%m%d_%H%M%S format creates a sortable timestamp like 20250119_143052.
Exercise 3: Daily Activity Summary ⭐⭐
Problem: Create a daily summary of Claude’s activities.
Solution:
#!/bin/bash
# Save as: .claude/hooks/daily_summary.sh
LOG_FILE="$HOME/.claude/activity.log"
SUMMARY_FILE="$HOME/.claude/daily_summary_$(date +%Y%m%d).txt"
# Count activities by type
{
echo "=== Claude Code Daily Summary - $(date) ==="
echo ""
echo "Files Modified:"
grep "$(date +%Y-%m-%d)" "$LOG_FILE" | grep -o "Modified .*" | sort | uniq -c
echo ""
echo "Total Operations: $(grep -c "$(date +%Y-%m-%d)" "$LOG_FILE")"
} > "$SUMMARY_FILE"
echo "Daily summary saved to: $SUMMARY_FILE"Exercise 4: Comprehensive File Tracking ⭐⭐⭐
Problem: Create a hook that logs file modifications to CSV, includes timestamp/filename/tool, and prevents modifications to files larger than 1MB.
Solution:
#!/bin/bash
# Save as: .claude/hooks/file_tracker.sh
CSV_FILE="$HOME/.claude/file_modifications.csv"
JSON_INPUT=$(cat)
# Initialize CSV if it doesn't exist
if [ ! -f "$CSV_FILE" ]; then
echo "timestamp,filename,tool,size_bytes,action" > "$CSV_FILE"
fi
# Extract data
TOOL_NAME=$(echo "$JSON_INPUT" | jq -r '.tool_name')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Process each file
for file in $CLAUDE_FILE_PATHS; do
if [ -f "$file" ]; then
SIZE=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
# Check size limit
if [ "$SIZE" -gt 1048576 ]; then
echo "{\"continue\": false, \"stopReason\": \"File $file exceeds 1MB limit ($SIZE bytes)\"}"
exit 2
fi
# Log to CSV
echo "$TIMESTAMP,\"$file\",$TOOL_NAME,$SIZE,modified" >> "$CSV_FILE"
fi
done
exit 0Intermediate Exercise Solutions
Exercise 1: JSON/YAML Formatting ⭐⭐
Problem: Add support for JSON and YAML formatting to the multi-language formatter.
Solution:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "for file in $CLAUDE_FILE_PATHS; do case \"$file\" in *.js|*.jsx|*.ts|*.tsx) prettier --write \"$file\" ;; *.py) black \"$file\" ;; *.go) gofmt -w \"$file\" ;; *.rs) rustfmt \"$file\" ;; *.json) jq --indent 2 . \"$file\" > \"$file.tmp\" && mv \"$file.tmp\" \"$file\" ;; *.yml|*.yaml) prettier --write \"$file\" || yamllint -f auto \"$file\" ;; esac; done"
}
]
}
]
}
}Exercise 2: Test Runner for Source Files ⭐⭐⭐
Problem: Create a hook that runs only the tests related to modified source files.
Solution:
#!/usr/bin/env python3
# Save as: .claude/hooks/smart_test_runner.py
import json
import sys
import os
import subprocess
from pathlib import Path
def find_test_file(source_file):
"""Find corresponding test file for a source file"""
path = Path(source_file)
stem = path.stem
# Common test file patterns
test_patterns = [
path.parent / f"test_{stem}.py",
path.parent / f"{stem}_test.py",
path.parent / "tests" / f"test_{stem}.py",
path.parent.parent / "tests" / path.parent.name / f"test_{stem}.py",
path.with_suffix(".test" + path.suffix),
path.parent / "__tests__" / f"{stem}.test{path.suffix}"
]
for test_path in test_patterns:
if test_path.exists():
return str(test_path)
return None
def run_tests(test_files):
"""Run tests based on file type"""
results = []
for test_file in test_files:
if test_file.endswith('.py'):
result = subprocess.run(['pytest', test_file, '-v'],
capture_output=True, text=True)
elif test_file.endswith(('.js', '.ts')):
result = subprocess.run(['npm', 'test', '--', test_file],
capture_output=True, text=True)
else:
continue
results.append({
'file': test_file,
'success': result.returncode == 0,
'output': result.stdout if result.returncode == 0 else result.stderr
})
return results
def main():
data = json.load(sys.stdin)
# Only run for source file modifications
if data.get('tool_name') not in ['Edit', 'Write', 'MultiEdit']:
return 0
files = os.environ.get('CLAUDE_FILE_PATHS', '').split()
test_files = []
for file in files:
# Skip if already a test file
if 'test' in file or 'spec' in file:
continue
test_file = find_test_file(file)
if test_file:
test_files.append(test_file)
if test_files:
print(f"Running tests for: {', '.join(test_files)}")
results = run_tests(test_files)
failed = [r for r in results if not r['success']]
if failed:
print(json.dumps({
'continue': False,
'stopReason': f"{len(failed)} tests failed"
}))
return 2
return 0
if __name__ == '__main__':
sys.exit(main())Exercise 3: Quality Gate ⭐⭐⭐⭐
Problem: Create a comprehensive quality gate hook.
Solution:
#!/bin/bash
# Save as: .claude/hooks/quality_gate.sh
ERRORS=0
WARNINGS=0
log_result() {
local status=$1
local message=$2
if [ "$status" -eq 0 ]; then
echo "✅ $message"
else
echo "❌ $message"
((ERRORS++))
fi
}
# 1. Format code
format_code() {
echo "=== Code Formatting ==="
for file in $CLAUDE_FILE_PATHS; do
case "$file" in
*.py)
black "$file" 2>/dev/null
log_result $? "Formatted Python: $file"
;;
*.js|*.ts|*.jsx|*.tsx)
prettier --write "$file" 2>/dev/null
log_result $? "Formatted JS/TS: $file"
;;
*.go)
gofmt -w "$file" 2>/dev/null
log_result $? "Formatted Go: $file"
;;
esac
done
}
# 2. Run linters
run_linters() {
echo -e "\n=== Linting ==="
for file in $CLAUDE_FILE_PATHS; do
case "$file" in
*.py)
pylint "$file" --exit-zero
log_result $? "Linted Python: $file"
;;
*.js|*.ts)
eslint "$file" --fix 2>/dev/null
log_result $? "Linted JS/TS: $file"
;;
esac
done
}
# 3. Run tests
run_tests() {
echo -e "\n=== Testing ==="
if [ -f "package.json" ] && grep -q "test" package.json; then
npm test
log_result $? "JavaScript tests"
fi
if [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then
pytest --tb=short
log_result $? "Python tests"
fi
}
# 4. Check coverage
check_coverage() {
echo -e "\n=== Code Coverage ==="
if [ -f "package.json" ] && grep -q "coverage" package.json; then
COVERAGE=$(npm run coverage --silent | grep -oE '[0-9]+%' | head -1 | tr -d '%')
if [ "$COVERAGE" -lt 80 ]; then
echo "⚠️ Coverage is ${COVERAGE}% (target: 80%)"
((WARNINGS++))
else
echo "✅ Coverage is ${COVERAGE}%"
fi
fi
}
# 5. Validate commit message
validate_commit() {
echo -e "\n=== Commit Validation ==="
# This would run if we're about to commit
if [ -n "$CLAUDE_COMMIT_MSG" ]; then
if [[ "$CLAUDE_COMMIT_MSG" =~ ^(feat|fix|docs|style|refactor|test|chore):.+ ]]; then
echo "✅ Commit message follows convention"
else
echo "❌ Commit message must follow format: type: description"
((ERRORS++))
fi
fi
}
# Execute all checks
echo "🚀 Running Quality Gate..."
echo "========================="
format_code
run_linters
run_tests
check_coverage
validate_commit
echo -e "\n========================="
echo "📊 Quality Gate Summary:"
echo " Errors: $ERRORS"
echo " Warnings: $WARNINGS"
if [ $ERRORS -gt 0 ]; then
echo -e "\n❌ Quality gate FAILED"
exit 2
else
echo -e "\n✅ Quality gate PASSED"
exit 0
fiAdvanced Exercise Solutions
Exercise 1: CI/CD with Auto-merge ⭐⭐⭐⭐
Problem: Extend the CI/CD integration to include automatic PR checks and merge if all tests pass.
Solution:
#!/bin/bash
# Save as: .claude/hooks/auto_merge_ci.sh
# Configuration
BRANCH_PREFIX="claude-auto"
REQUIRED_CHECKS=("test" "lint" "security")
create_and_push_branch() {
BRANCH="${BRANCH_PREFIX}-$(date +%s)"
git checkout -b "$BRANCH"
git add -A
git commit -m "Claude Code: Automated improvements
- Automated formatting applied
- Tests verified
- Security checks passed"
git push -u origin "$BRANCH"
echo "$BRANCH"
}
wait_for_checks() {
local pr_number=$1
local max_attempts=30
local attempt=0
echo "Waiting for PR checks to complete..."
while [ $attempt -lt $max_attempts ]; do
# Get check status
STATUS=$(gh pr checks "$pr_number" --json state -q '.[] | select(.state != "PENDING") | .state' | sort -u)
if [[ "$STATUS" == "FAILURE" ]]; then
echo "❌ PR checks failed"
return 1
elif [[ -z "$STATUS" ]] || [[ "$STATUS" == "SUCCESS" ]]; then
# Verify all required checks passed
for check in "${REQUIRED_CHECKS[@]}"; do
if ! gh pr checks "$pr_number" | grep -q "$check.*✓"; then
echo "⏳ Waiting for $check..."
sleep 10
((attempt++))
continue 2
fi
done
echo "✅ All checks passed!"
return 0
fi
sleep 10
((attempt++))
done
echo "⏱️ Timeout waiting for checks"
return 1
}
main() {
# Check for changes
if git diff --quiet && git diff --staged --quiet; then
echo "No changes to process"
exit 0
fi
# Create branch and PR
BRANCH=$(create_and_push_branch)
PR_URL=$(gh pr create \
--title "Claude Code: Automated improvements" \
--body "This PR contains automated improvements by Claude Code.
### Automated Actions:
- [x] Code formatting
- [x] Test verification
- [x] Security scanning
### Review Requirements:
This PR will auto-merge if all checks pass." \
--label "claude-code,automated" \
--assignee "@me")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "Created PR #$PR_NUMBER: $PR_URL"
# Wait for and monitor checks
if wait_for_checks "$PR_NUMBER"; then
echo "🎉 Auto-merging PR #$PR_NUMBER"
gh pr merge "$PR_NUMBER" --auto --squash --delete-branch
# Switch back to main branch
git checkout main
git pull
else
echo "⚠️ Manual review required for PR #$PR_NUMBER"
echo "View at: $PR_URL"
fi
}
mainExercise 2: DevOps Automation System ⭐⭐⭐⭐⭐
Problem: Create a complete DevOps automation system.
Solution:
#!/usr/bin/env python3
# Save as: .claude/hooks/devops_automation.py
import json
import sys
import subprocess
import asyncio
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any
import yaml
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('DevOpsAutomation')
class DevOpsAutomation:
def __init__(self):
self.config = self.load_config()
self.metrics = {}
def load_config(self) -> Dict:
"""Load DevOps configuration"""
config_path = Path('.claude/devops_config.yaml')
if config_path.exists():
with open(config_path) as f:
return yaml.safe_load(f)
return {
'repositories': [],
'security_scan': True,
'performance_threshold': 0.9,
'deployment_strategy': 'blue-green',
'rollback_on_failure': True
}
async def monitor_repositories(self) -> List[Dict]:
"""Monitor multiple repositories for changes"""
changes = []
for repo in self.config['repositories']:
cmd = f"cd {repo['path']} && git fetch && git log HEAD..origin/main --oneline"
result = await self.run_command(cmd)
if result['stdout']:
changes.append({
'repo': repo['name'],
'changes': result['stdout'].strip().split('\n')
})
return changes
async def run_security_scan(self, path: str) -> Dict:
"""Run comprehensive security scanning"""
scanners = [
('trivy', f'trivy fs --security-checks vuln {path}'),
('bandit', f'bandit -r {path} -f json'),
('safety', 'safety check --json'),
('npm-audit', 'npm audit --json')
]
results = {}
for scanner_name, cmd in scanners:
try:
result = await self.run_command(cmd)
results[scanner_name] = {
'success': result['returncode'] == 0,
'output': result['stdout']
}
except Exception as e:
results[scanner_name] = {
'success': False,
'error': str(e)
}
return results
async def run_performance_tests(self) -> Dict:
"""Execute performance tests"""
tests = {
'load_test': 'k6 run scripts/load_test.js --out json=results.json',
'benchmark': 'python scripts/benchmark.py',
'memory_profile': 'python -m memory_profiler scripts/profile_memory.py'
}
results = {}
for test_name, cmd in tests.items():
result = await self.run_command(cmd)
results[test_name] = {
'success': result['returncode'] == 0,
'metrics': self.parse_metrics(result['stdout'])
}
return results
def generate_deployment_plan(self, changes: List[Dict]) -> Dict:
"""Generate deployment plan based on dependencies"""
plan = {
'strategy': self.config['deployment_strategy'],
'steps': [],
'rollback_points': []
}
# Analyze dependencies
dependency_graph = self.build_dependency_graph(changes)
# Topological sort for deployment order
deployment_order = self.topological_sort(dependency_graph)
for service in deployment_order:
plan['steps'].append({
'service': service,
'actions': [
'backup_current_version',
'deploy_new_version',
'health_check',
'smoke_test'
],
'rollback_point': f"checkpoint_{service}"
})
plan['rollback_points'].append(f"checkpoint_{service}")
return plan
async def execute_blue_green_deployment(self, plan: Dict) -> Dict:
"""Execute blue-green deployment"""
results = {'deployments': []}
for step in plan['steps']:
service = step['service']
logger.info(f"Deploying {service}...")
# Deploy to blue environment
blue_result = await self.deploy_to_environment(service, 'blue')
if blue_result['success']:
# Run tests on blue
test_result = await self.test_environment(service, 'blue')
if test_result['success']:
# Switch traffic
switch_result = await self.switch_traffic(service, 'blue')
if switch_result['success']:
# Successful deployment
results['deployments'].append({
'service': service,
'status': 'success',
'environment': 'blue'
})
continue
# Rollback on failure
if self.config['rollback_on_failure']:
await self.rollback_service(service)
results['deployments'].append({
'service': service,
'status': 'rolled_back',
'reason': 'deployment_failed'
})
# Stop further deployments
results['halted'] = True
break
return results
async def monitor_production_metrics(self) -> Dict:
"""Monitor production metrics post-deployment"""
metrics = {
'response_time': [],
'error_rate': [],
'cpu_usage': [],
'memory_usage': []
}
# Collect metrics for 5 minutes
for _ in range(30): # 30 * 10s = 5 minutes
current_metrics = await self.collect_current_metrics()
for key, value in current_metrics.items():
metrics[key].append(value)
# Check for anomalies
if self.detect_anomalies(metrics):
return {
'status': 'anomaly_detected',
'metrics': metrics,
'action': 'automatic_rollback'
}
await asyncio.sleep(10)
return {
'status': 'healthy',
'metrics': metrics
}
async def automatic_rollback(self, service: str) -> Dict:
"""Perform automatic rollback"""
logger.warning(f"Initiating automatic rollback for {service}")
steps = [
('notify_team', self.notify_team(f"Rollback initiated for {service}")),
('switch_traffic', self.switch_traffic(service, 'green')),
('verify_health', self.test_environment(service, 'green')),
('cleanup', self.cleanup_failed_deployment(service))
]
results = {}
for step_name, coroutine in steps:
result = await coroutine
results[step_name] = result
if not result.get('success'):
logger.error(f"Rollback step {step_name} failed")
break
return results
# Helper methods
async def run_command(self, cmd: str) -> Dict:
"""Run shell command asynchronously"""
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
return {
'returncode': proc.returncode,
'stdout': stdout.decode(),
'stderr': stderr.decode()
}
def build_dependency_graph(self, changes: List[Dict]) -> Dict:
"""Build service dependency graph"""
# Implementation depends on your architecture
return {
'frontend': ['api'],
'api': ['auth', 'database'],
'auth': ['database'],
'database': []
}
def topological_sort(self, graph: Dict) -> List[str]:
"""Topological sort for deployment order"""
visited = set()
stack = []
def dfs(node):
visited.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
dfs(neighbor)
stack.append(node)
for node in graph:
if node not in visited:
dfs(node)
return stack[::-1]
def detect_anomalies(self, metrics: Dict) -> bool:
"""Detect anomalies in metrics"""
# Simple threshold-based detection
if metrics['error_rate'] and max(metrics['error_rate']) > 0.05:
return True
if metrics['response_time'] and max(metrics['response_time']) > 1000:
return True
return False
async def main():
automation = DevOpsAutomation()
try:
# 1. Monitor repositories
logger.info("Monitoring repositories...")
changes = await automation.monitor_repositories()
if not changes:
logger.info("No changes detected")
return 0
# 2. Security scanning
logger.info("Running security scans...")
security_results = await automation.run_security_scan('.')
vulnerabilities = sum(
1 for r in security_results.values()
if not r['success']
)
if vulnerabilities > 0:
logger.error(f"Security scan found {vulnerabilities} issues")
return 2
# 3. Performance testing
logger.info("Running performance tests...")
perf_results = await automation.run_performance_tests()
# 4. Generate deployment plan
logger.info("Generating deployment plan...")
deployment_plan = automation.generate_deployment_plan(changes)
# 5. Execute deployment
logger.info("Executing blue-green deployment...")
deployment_results = await automation.execute_blue_green_deployment(
deployment_plan
)
# 6. Monitor production
logger.info("Monitoring production metrics...")
monitoring_results = await automation.monitor_production_metrics()
if monitoring_results['status'] == 'anomaly_detected':
# 7. Automatic rollback
logger.warning("Anomaly detected, initiating rollback...")
for deployment in deployment_results['deployments']:
if deployment['status'] == 'success':
await automation.automatic_rollback(deployment['service'])
logger.info("DevOps automation completed successfully")
return 0
except Exception as e:
logger.error(f"Automation failed: {e}")
return 1
if __name__ == '__main__':
# Read Claude Code input
data = json.load(sys.stdin)
# Run automation
exit_code = asyncio.run(main())
sys.exit(exit_code)Tips for All Exercises
- Start Simple: Test basic functionality before adding complexity
- Use Logging: Add debug logs to understand what’s happening
- Handle Errors: Always include error handling
- Test Thoroughly: Test with various inputs and edge cases
- Document: Add comments explaining complex logic
Remember: These solutions are starting points. Adapt them to your specific needs and environment!
Related Resources
- Beginner Examples - Start here
- Intermediate Examples - Build skills
- Advanced Examples - Master hooks
- Quick Reference - Keep handy
- Debugging Guide - Fix issues