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