Subchapter 6.3
references/patterns.mdMarkdown7 KBView on GitHub
This reference provides common, proven patterns for implementing Claude Code hooks. Use these patterns as starting points for typical hook use cases.
Block dangerous file writes using prompt-based hooks:
{
"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.
Ensure tests run before stopping:
{
"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.
Load project-specific context at session start:
{
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
}
]
}
]
}Example script (load-context.sh):
#!/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"
fiUse for: Automatically detecting and configuring project-specific settings.
Log all notifications for audit or analysis:
{
"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.
Monitor and validate MCP tool usage:
{
"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.
Ensure project builds after code changes:
{
"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.
Ask user before dangerous operations:
{
"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.
Run linters or formatters on file edits:
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-quality.sh"
}
]
}
]
}Example script (check-quality.sh):
#!/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
fiUse for: Automatic code quality enforcement.
Combine multiple patterns for comprehensive protection:
{
"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.
Create hooks that only run when explicitly enabled via flag files:
#!/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:
# Enable the hook
touch .enable-security-scan
# Disable the hook
rm .enable-security-scanUse for:
Note: Must restart Claude Code after creating/removing flag files for hooks to recognize changes.
Use JSON configuration to control hook behavior:
#!/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
fiConfiguration file (.claude/my-plugin.local.json):
{
"strictMode": true,
"maxFileSize": 500000,
"allowedPaths": ["/tmp", "/home/user/projects"]
}Use for: