|
Download plugins/plugin-dev/skills/hook-development/references/advanced.md from SaylorTwift/claude-code: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/SaylorTwift/claude-code/resolve/main/plugins/plugin-dev/skills/hook-development/references/advanced.md
- Command line
-
hf download hf://SaylorTwift/claude-code/plugins/plugin-dev/skills/hook-development/references/advanced.md
-
curl -L -o advanced.md https://huggingface.co/SaylorTwift/claude-code/resolve/main/plugins/plugin-dev/skills/hook-development/references/advanced.md
10.1 kB
| # 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. | |