# 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.