Initial commit
This commit is contained in:
479
skills/hook-development/references/advanced.md
Normal file
479
skills/hook-development/references/advanced.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# Advanced Hook Use Cases
|
||||
|
||||
This reference covers advanced hook patterns and techniques for sophisticated automation workflows.
|
||||
|
||||
## Multi-Stage Validation
|
||||
|
||||
Combine command and prompt hooks for layered validation:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/quick-check.sh",
|
||||
"timeout": 5
|
||||
},
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Deep analysis of bash command: $TOOL_INPUT",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use case:** Fast deterministic checks followed by intelligent analysis
|
||||
|
||||
**Example quick-check.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.tool_input.command')
|
||||
|
||||
# Immediate approval for safe commands
|
||||
if [[ "$command" =~ ^(ls|pwd|echo|date|whoami)$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Let prompt hook handle complex cases
|
||||
exit 0
|
||||
```
|
||||
|
||||
The command hook quickly approves obviously safe commands, while the prompt hook analyzes everything else.
|
||||
|
||||
## Conditional Hook Execution
|
||||
|
||||
Execute hooks based on environment or context:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Only run in CI environment
|
||||
if [ -z "$CI" ]; then
|
||||
echo '{"continue": true}' # Skip in non-CI
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run validation logic in CI
|
||||
input=$(cat)
|
||||
# ... validation code ...
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Different behavior in CI vs local development
|
||||
- Project-specific validation
|
||||
- User-specific rules
|
||||
|
||||
**Example: Skip certain checks for trusted users:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Skip detailed checks for admin users
|
||||
if [ "$USER" = "admin" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Full validation for other users
|
||||
input=$(cat)
|
||||
# ... validation code ...
|
||||
```
|
||||
|
||||
## Hook Chaining via State
|
||||
|
||||
Share state between hooks using temporary files:
|
||||
|
||||
```bash
|
||||
# Hook 1: Analyze and save state
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.tool_input.command')
|
||||
|
||||
# Analyze command
|
||||
risk_level=$(calculate_risk "$command")
|
||||
echo "$risk_level" > /tmp/hook-state-$$
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
```bash
|
||||
# Hook 2: Use saved state
|
||||
#!/bin/bash
|
||||
risk_level=$(cat /tmp/hook-state-$$ 2>/dev/null || echo "unknown")
|
||||
|
||||
if [ "$risk_level" = "high" ]; then
|
||||
echo "High risk operation detected" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Important:** This only works for sequential hook events (e.g., PreToolUse then PostToolUse), not parallel hooks.
|
||||
|
||||
## Dynamic Hook Configuration
|
||||
|
||||
Modify hook behavior based on project configuration:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
cd "$CLAUDE_PROJECT_DIR" || exit 1
|
||||
|
||||
# Read project-specific config
|
||||
if [ -f ".claude-hooks-config.json" ]; then
|
||||
strict_mode=$(jq -r '.strict_mode' .claude-hooks-config.json)
|
||||
|
||||
if [ "$strict_mode" = "true" ]; then
|
||||
# Apply strict validation
|
||||
# ...
|
||||
else
|
||||
# Apply lenient validation
|
||||
# ...
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Example .claude-hooks-config.json:**
|
||||
```json
|
||||
{
|
||||
"strict_mode": true,
|
||||
"allowed_commands": ["ls", "pwd", "grep"],
|
||||
"forbidden_paths": ["/etc", "/sys"]
|
||||
}
|
||||
```
|
||||
|
||||
## Context-Aware Prompt Hooks
|
||||
|
||||
Use transcript and session context for intelligent decisions:
|
||||
|
||||
```json
|
||||
{
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Review the full transcript at $TRANSCRIPT_PATH. Check: 1) Were tests run after code changes? 2) Did the build succeed? 3) Were all user questions answered? 4) Is there any unfinished work? Return 'approve' only if everything is complete."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The LLM can read the transcript file and make context-aware decisions.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Caching Validation Results
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
cache_key=$(echo -n "$file_path" | md5sum | cut -d' ' -f1)
|
||||
cache_file="/tmp/hook-cache-$cache_key"
|
||||
|
||||
# Check cache
|
||||
if [ -f "$cache_file" ]; then
|
||||
cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file")))
|
||||
if [ "$cache_age" -lt 300 ]; then # 5 minute cache
|
||||
cat "$cache_file"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Perform validation
|
||||
result='{"decision": "approve"}'
|
||||
|
||||
# Cache result
|
||||
echo "$result" > "$cache_file"
|
||||
echo "$result"
|
||||
```
|
||||
|
||||
### Parallel Execution Optimization
|
||||
|
||||
Since hooks run in parallel, design them to be independent:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash check-size.sh", // Independent
|
||||
"timeout": 2
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash check-path.sh", // Independent
|
||||
"timeout": 2
|
||||
},
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Check content safety", // Independent
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
All three hooks run simultaneously, reducing total latency.
|
||||
|
||||
## Cross-Event Workflows
|
||||
|
||||
Coordinate hooks across different events:
|
||||
|
||||
**SessionStart - Set up tracking:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Initialize session tracking
|
||||
echo "0" > /tmp/test-count-$$
|
||||
echo "0" > /tmp/build-count-$$
|
||||
```
|
||||
|
||||
**PostToolUse - Track events:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
|
||||
if [ "$tool_name" = "Bash" ]; then
|
||||
command=$(echo "$input" | jq -r '.tool_result')
|
||||
if [[ "$command" == *"test"* ]]; then
|
||||
count=$(cat /tmp/test-count-$$ 2>/dev/null || echo "0")
|
||||
echo $((count + 1)) > /tmp/test-count-$$
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Stop - Verify based on tracking:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
test_count=$(cat /tmp/test-count-$$ 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$test_count" -eq 0 ]; then
|
||||
echo '{"decision": "block", "reason": "No tests were run"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
## Integration with External Systems
|
||||
|
||||
### Slack Notifications
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
decision="blocked"
|
||||
|
||||
# Send notification to Slack
|
||||
curl -X POST "$SLACK_WEBHOOK" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"text\": \"Hook ${decision} ${tool_name} operation\"}" \
|
||||
2>/dev/null
|
||||
|
||||
echo '{"decision": "deny"}' >&2
|
||||
exit 2
|
||||
```
|
||||
|
||||
### Database Logging
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
|
||||
# Log to database
|
||||
psql "$DATABASE_URL" -c "INSERT INTO hook_logs (event, data) VALUES ('PreToolUse', '$input')" \
|
||||
2>/dev/null
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
### Metrics Collection
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
|
||||
# Send metrics to monitoring system
|
||||
echo "hook.pretooluse.${tool_name}:1|c" | nc -u -w1 statsd.local 8125
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.tool_input.command')
|
||||
|
||||
# Track command frequency
|
||||
rate_file="/tmp/hook-rate-$$"
|
||||
current_minute=$(date +%Y%m%d%H%M)
|
||||
|
||||
if [ -f "$rate_file" ]; then
|
||||
last_minute=$(head -1 "$rate_file")
|
||||
count=$(tail -1 "$rate_file")
|
||||
|
||||
if [ "$current_minute" = "$last_minute" ]; then
|
||||
if [ "$count" -gt 10 ]; then
|
||||
echo '{"decision": "deny", "reason": "Rate limit exceeded"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
count=$((count + 1))
|
||||
else
|
||||
count=1
|
||||
fi
|
||||
else
|
||||
count=1
|
||||
fi
|
||||
|
||||
echo "$current_minute" > "$rate_file"
|
||||
echo "$count" >> "$rate_file"
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
### Audit Logging
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
timestamp=$(date -Iseconds)
|
||||
|
||||
# Append to audit log
|
||||
echo "$timestamp | $USER | $tool_name | $input" >> ~/.claude/audit.log
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
### Secret Detection
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
content=$(echo "$input" | jq -r '.tool_input.content')
|
||||
|
||||
# Check for common secret patterns
|
||||
if echo "$content" | grep -qE "(api[_-]?key|password|secret|token).{0,20}['\"]?[A-Za-z0-9]{20,}"; then
|
||||
echo '{"decision": "deny", "reason": "Potential secret detected in content"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
## Testing Advanced Hooks
|
||||
|
||||
### Unit Testing Hook Scripts
|
||||
|
||||
```bash
|
||||
# test-hook.sh
|
||||
#!/bin/bash
|
||||
|
||||
# Test 1: Approve safe command
|
||||
result=$(echo '{"tool_input": {"command": "ls"}}' | bash validate-bash.sh)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Test 1 passed"
|
||||
else
|
||||
echo "✗ Test 1 failed"
|
||||
fi
|
||||
|
||||
# Test 2: Block dangerous command
|
||||
result=$(echo '{"tool_input": {"command": "rm -rf /"}}' | bash validate-bash.sh)
|
||||
if [ $? -eq 2 ]; then
|
||||
echo "✓ Test 2 passed"
|
||||
else
|
||||
echo "✗ Test 2 failed"
|
||||
fi
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
Create test scenarios that exercise the full hook workflow:
|
||||
|
||||
```bash
|
||||
# integration-test.sh
|
||||
#!/bin/bash
|
||||
|
||||
# Set up test environment
|
||||
export CLAUDE_PROJECT_DIR="/tmp/test-project"
|
||||
export CLAUDE_PLUGIN_ROOT="$(pwd)"
|
||||
mkdir -p "$CLAUDE_PROJECT_DIR"
|
||||
|
||||
# Test SessionStart hook
|
||||
echo '{}' | bash hooks/session-start.sh
|
||||
if [ -f "/tmp/session-initialized" ]; then
|
||||
echo "✓ SessionStart hook works"
|
||||
else
|
||||
echo "✗ SessionStart hook failed"
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -rf "$CLAUDE_PROJECT_DIR"
|
||||
```
|
||||
|
||||
## Best Practices for Advanced Hooks
|
||||
|
||||
1. **Keep hooks independent**: Don't rely on execution order
|
||||
2. **Use timeouts**: Set appropriate limits for each hook type
|
||||
3. **Handle errors gracefully**: Provide clear error messages
|
||||
4. **Document complexity**: Explain advanced patterns in README
|
||||
5. **Test thoroughly**: Cover edge cases and failure modes
|
||||
6. **Monitor performance**: Track hook execution time
|
||||
7. **Version configuration**: Use version control for hook configs
|
||||
8. **Provide escape hatches**: Allow users to bypass hooks when needed
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### ❌ Assuming Hook Order
|
||||
|
||||
```bash
|
||||
# BAD: Assumes hooks run in specific order
|
||||
# Hook 1 saves state, Hook 2 reads it
|
||||
# This can fail because hooks run in parallel!
|
||||
```
|
||||
|
||||
### ❌ Long-Running Hooks
|
||||
|
||||
```bash
|
||||
# BAD: Hook takes 2 minutes to run
|
||||
sleep 120
|
||||
# This will timeout and block the workflow
|
||||
```
|
||||
|
||||
### ❌ Uncaught Exceptions
|
||||
|
||||
```bash
|
||||
# BAD: Script crashes on unexpected input
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
cat "$file_path" # Fails if file doesn't exist
|
||||
```
|
||||
|
||||
### ✅ Proper Error Handling
|
||||
|
||||
```bash
|
||||
# GOOD: Handles errors gracefully
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
if [ ! -f "$file_path" ]; then
|
||||
echo '{"continue": true, "systemMessage": "File not found, skipping check"}' >&2
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Advanced hook patterns enable sophisticated automation while maintaining reliability and performance. Use these techniques when basic hooks are insufficient, but always prioritize simplicity and maintainability.
|
||||
369
skills/hook-development/references/migration.md
Normal file
369
skills/hook-development/references/migration.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Migrating from Basic to Advanced Hooks
|
||||
|
||||
This guide shows how to migrate from basic command hooks to advanced prompt-based hooks for better maintainability and flexibility.
|
||||
|
||||
## Why Migrate?
|
||||
|
||||
Prompt-based hooks offer several advantages:
|
||||
|
||||
- **Natural language reasoning**: LLM understands context and intent
|
||||
- **Better edge case handling**: Adapts to unexpected scenarios
|
||||
- **No bash scripting required**: Simpler to write and maintain
|
||||
- **More flexible validation**: Can handle complex logic without coding
|
||||
|
||||
## Migration Example: Bash Command Validation
|
||||
|
||||
### Before (Basic Command Hook)
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash validate-bash.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Script (validate-bash.sh):**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.tool_input.command')
|
||||
|
||||
# Hard-coded validation logic
|
||||
if [[ "$command" == *"rm -rf"* ]]; then
|
||||
echo "Dangerous command detected" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- Only checks for exact "rm -rf" pattern
|
||||
- Doesn't catch variations like `rm -fr` or `rm -r -f`
|
||||
- Misses other dangerous commands (`dd`, `mkfs`, etc.)
|
||||
- No context awareness
|
||||
- Requires bash scripting knowledge
|
||||
|
||||
### After (Advanced Prompt Hook)
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Command: $TOOL_INPUT.command. Analyze for: 1) Destructive operations (rm -rf, dd, mkfs, etc) 2) Privilege escalation (sudo) 3) Network operations without user consent. Return 'approve' or 'deny' with explanation.",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Catches all variations and patterns
|
||||
- Understands intent, not just literal strings
|
||||
- No script file needed
|
||||
- Easy to extend with new criteria
|
||||
- Context-aware decisions
|
||||
- Natural language explanation in denial
|
||||
|
||||
## Migration Example: File Write Validation
|
||||
|
||||
### Before (Basic Command Hook)
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash validate-write.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Script (validate-write.sh):**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
|
||||
# Check for path traversal
|
||||
if [[ "$file_path" == *".."* ]]; then
|
||||
echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Check for system paths
|
||||
if [[ "$file_path" == "/etc/"* ]] || [[ "$file_path" == "/sys/"* ]]; then
|
||||
echo '{"decision": "deny", "reason": "System file"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- Hard-coded path patterns
|
||||
- Doesn't understand symlinks
|
||||
- Missing edge cases (e.g., `/etc` vs `/etc/`)
|
||||
- No consideration of file content
|
||||
|
||||
### After (Advanced Prompt Hook)
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "File path: $TOOL_INPUT.file_path. Content preview: $TOOL_INPUT.content (first 200 chars). Verify: 1) Not system directories (/etc, /sys, /usr) 2) Not credentials (.env, tokens, secrets) 3) No path traversal 4) Content doesn't expose secrets. Return 'approve' or 'deny'."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Context-aware (considers content too)
|
||||
- Handles symlinks and edge cases
|
||||
- Natural understanding of "system directories"
|
||||
- Can detect secrets in content
|
||||
- Easy to extend criteria
|
||||
|
||||
## When to Keep Command Hooks
|
||||
|
||||
Command hooks still have their place:
|
||||
|
||||
### 1. Deterministic Performance Checks
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Check file size quickly
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
size=$(stat -f%z "$file_path" 2>/dev/null || stat -c%s "$file_path" 2>/dev/null)
|
||||
|
||||
if [ "$size" -gt 10000000 ]; then
|
||||
echo '{"decision": "deny", "reason": "File too large"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Use command hooks when:** Validation is purely mathematical or deterministic.
|
||||
|
||||
### 2. External Tool Integration
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Run security scanner
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
scan_result=$(security-scanner "$file_path")
|
||||
|
||||
if [ "$?" -ne 0 ]; then
|
||||
echo "Security scan failed: $scan_result" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Use command hooks when:** Integrating with external tools that provide yes/no answers.
|
||||
|
||||
### 3. Very Fast Checks (< 50ms)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Quick regex check
|
||||
command=$(echo "$input" | jq -r '.tool_input.command')
|
||||
|
||||
if [[ "$command" =~ ^(ls|pwd|echo)$ ]]; then
|
||||
exit 0 # Safe commands
|
||||
fi
|
||||
```
|
||||
|
||||
**Use command hooks when:** Performance is critical and logic is simple.
|
||||
|
||||
## Hybrid Approach
|
||||
|
||||
Combine both for multi-stage validation:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/quick-check.sh",
|
||||
"timeout": 5
|
||||
},
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Deep analysis of bash command: $TOOL_INPUT",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The command hook does fast deterministic checks, while the prompt hook handles complex reasoning.
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
When migrating hooks:
|
||||
|
||||
- [ ] Identify the validation logic in the command hook
|
||||
- [ ] Convert hard-coded patterns to natural language criteria
|
||||
- [ ] Test with edge cases the old hook missed
|
||||
- [ ] Verify LLM understands the intent
|
||||
- [ ] Set appropriate timeout (usually 15-30s for prompt hooks)
|
||||
- [ ] Document the new hook in README
|
||||
- [ ] Remove or archive old script files
|
||||
|
||||
## Migration Tips
|
||||
|
||||
1. **Start with one hook**: Don't migrate everything at once
|
||||
2. **Test thoroughly**: Verify prompt hook catches what command hook caught
|
||||
3. **Look for improvements**: Use migration as opportunity to enhance validation
|
||||
4. **Keep scripts for reference**: Archive old scripts in case you need to reference the logic
|
||||
5. **Document reasoning**: Explain why prompt hook is better in README
|
||||
|
||||
## Complete Migration Example
|
||||
|
||||
### Original Plugin Structure
|
||||
|
||||
```
|
||||
my-plugin/
|
||||
├── .claude-plugin/plugin.json
|
||||
├── hooks/hooks.json
|
||||
└── scripts/
|
||||
├── validate-bash.sh
|
||||
├── validate-write.sh
|
||||
└── check-tests.sh
|
||||
```
|
||||
|
||||
### After Migration
|
||||
|
||||
```
|
||||
my-plugin/
|
||||
├── .claude-plugin/plugin.json
|
||||
├── hooks/hooks.json # Now uses prompt hooks
|
||||
└── scripts/ # Archive or delete
|
||||
└── archive/
|
||||
├── validate-bash.sh
|
||||
├── validate-write.sh
|
||||
└── check-tests.sh
|
||||
```
|
||||
|
||||
### Updated hooks.json
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Validate bash command safety: destructive ops, privilege escalation, network access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Validate file write safety: system paths, credentials, path traversal, content secrets"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Verify tests were run if code was modified"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** Simpler, more maintainable, more powerful.
|
||||
|
||||
## Common Migration Patterns
|
||||
|
||||
### Pattern: String Contains → Natural Language
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
if [[ "$command" == *"sudo"* ]]; then
|
||||
echo "Privilege escalation" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
"Check for privilege escalation (sudo, su, etc)"
|
||||
```
|
||||
|
||||
### Pattern: Regex → Intent
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
if [[ "$file" =~ \.(env|secret|key|token)$ ]]; then
|
||||
echo "Credential file" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
"Verify not writing to credential files (.env, secrets, keys, tokens)"
|
||||
```
|
||||
|
||||
### Pattern: Multiple Conditions → Criteria List
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
if [ condition1 ] || [ condition2 ] || [ condition3 ]; then
|
||||
echo "Invalid" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
"Check: 1) condition1 2) condition2 3) condition3. Deny if any fail."
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Migrating to prompt-based hooks makes plugins more maintainable, flexible, and powerful. Reserve command hooks for deterministic checks and external tool integration.
|
||||
346
skills/hook-development/references/patterns.md
Normal file
346
skills/hook-development/references/patterns.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Common Hook Patterns
|
||||
|
||||
This reference provides common, proven patterns for implementing Claude Code hooks. Use these patterns as starting points for typical hook use cases.
|
||||
|
||||
## Pattern 1: Security Validation
|
||||
|
||||
Block dangerous file writes using prompt-based hooks:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "File path: $TOOL_INPUT.file_path. Verify: 1) Not in /etc or system directories 2) Not .env or credentials 3) Path doesn't contain '..' traversal. Return 'approve' or 'deny'."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Preventing writes to sensitive files or system directories.
|
||||
|
||||
## Pattern 2: Test Enforcement
|
||||
|
||||
Ensure tests run before stopping:
|
||||
|
||||
```json
|
||||
{
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Review transcript. If code was modified (Write/Edit tools used), verify tests were executed. If no tests were run, block with reason 'Tests must be run after code changes'."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Enforcing quality standards and preventing incomplete work.
|
||||
|
||||
## Pattern 3: Context Loading
|
||||
|
||||
Load project-specific context at session start:
|
||||
|
||||
```json
|
||||
{
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example script (load-context.sh):**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
cd "$CLAUDE_PROJECT_DIR" || exit 1
|
||||
|
||||
# Detect project type
|
||||
if [ -f "package.json" ]; then
|
||||
echo "📦 Node.js project detected"
|
||||
echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
|
||||
elif [ -f "Cargo.toml" ]; then
|
||||
echo "🦀 Rust project detected"
|
||||
echo "export PROJECT_TYPE=rust" >> "$CLAUDE_ENV_FILE"
|
||||
fi
|
||||
```
|
||||
|
||||
**Use for:** Automatically detecting and configuring project-specific settings.
|
||||
|
||||
## Pattern 4: Notification Logging
|
||||
|
||||
Log all notifications for audit or analysis:
|
||||
|
||||
```json
|
||||
{
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/log-notification.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Tracking user notifications or integration with external logging systems.
|
||||
|
||||
## Pattern 5: MCP Tool Monitoring
|
||||
|
||||
Monitor and validate MCP tool usage:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "mcp__.*__delete.*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Deletion operation detected. Verify: Is this deletion intentional? Can it be undone? Are there backups? Return 'approve' only if safe."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Protecting against destructive MCP operations.
|
||||
|
||||
## Pattern 6: Build Verification
|
||||
|
||||
Ensure project builds after code changes:
|
||||
|
||||
```json
|
||||
{
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Check if code was modified. If Write/Edit tools were used, verify the project was built (npm run build, cargo build, etc). If not built, block and request build."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Catching build errors before committing or stopping work.
|
||||
|
||||
## Pattern 7: Permission Confirmation
|
||||
|
||||
Ask user before dangerous operations:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Command: $TOOL_INPUT.command. If command contains 'rm', 'delete', 'drop', or other destructive operations, return 'ask' to confirm with user. Otherwise 'approve'."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** User confirmation on potentially destructive commands.
|
||||
|
||||
## Pattern 8: Code Quality Checks
|
||||
|
||||
Run linters or formatters on file edits:
|
||||
|
||||
```json
|
||||
{
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-quality.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example script (check-quality.sh):**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
|
||||
# Run linter if applicable
|
||||
if [[ "$file_path" == *.js ]] || [[ "$file_path" == *.ts ]]; then
|
||||
npx eslint "$file_path" 2>&1 || true
|
||||
fi
|
||||
```
|
||||
|
||||
**Use for:** Automatic code quality enforcement.
|
||||
|
||||
## Pattern Combinations
|
||||
|
||||
Combine multiple patterns for comprehensive protection:
|
||||
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Validate file write safety"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Validate bash command safety"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Verify tests run and build succeeded"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This provides multi-layered protection and automation.
|
||||
|
||||
## Pattern 9: Temporarily Active Hooks
|
||||
|
||||
Create hooks that only run when explicitly enabled via flag files:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Hook only active when flag file exists
|
||||
FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-security-scan"
|
||||
|
||||
if [ ! -f "$FLAG_FILE" ]; then
|
||||
# Quick exit when disabled
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Flag present, run validation
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
||||
|
||||
# Run security scan
|
||||
security-scanner "$file_path"
|
||||
```
|
||||
|
||||
**Activation:**
|
||||
```bash
|
||||
# Enable the hook
|
||||
touch .enable-security-scan
|
||||
|
||||
# Disable the hook
|
||||
rm .enable-security-scan
|
||||
```
|
||||
|
||||
**Use for:**
|
||||
- Temporary debugging hooks
|
||||
- Feature flags for development
|
||||
- Project-specific validation that's opt-in
|
||||
- Performance-intensive checks only when needed
|
||||
|
||||
**Note:** Must restart Claude Code after creating/removing flag files for hooks to recognize changes.
|
||||
|
||||
## Pattern 10: Configuration-Driven Hooks
|
||||
|
||||
Use JSON configuration to control hook behavior:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/my-plugin.local.json"
|
||||
|
||||
# Read configuration
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
strict_mode=$(jq -r '.strictMode // false' "$CONFIG_FILE")
|
||||
max_file_size=$(jq -r '.maxFileSize // 1000000' "$CONFIG_FILE")
|
||||
else
|
||||
# Defaults
|
||||
strict_mode=false
|
||||
max_file_size=1000000
|
||||
fi
|
||||
|
||||
# Skip if not in strict mode
|
||||
if [ "$strict_mode" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Apply configured limits
|
||||
input=$(cat)
|
||||
file_size=$(echo "$input" | jq -r '.tool_input.content | length')
|
||||
|
||||
if [ "$file_size" -gt "$max_file_size" ]; then
|
||||
echo '{"decision": "deny", "reason": "File exceeds configured size limit"}' >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
**Configuration file (.claude/my-plugin.local.json):**
|
||||
```json
|
||||
{
|
||||
"strictMode": true,
|
||||
"maxFileSize": 500000,
|
||||
"allowedPaths": ["/tmp", "/home/user/projects"]
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:**
|
||||
- User-configurable hook behavior
|
||||
- Per-project settings
|
||||
- Team-specific rules
|
||||
- Dynamic validation criteria
|
||||
Reference in New Issue
Block a user