surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
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:
{
"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:
{
"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:
{
"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"
fi
Use for: Automatically detecting and configuring project-specific settings.
Pattern 4: Notification Logging
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.
Pattern 5: MCP Tool Monitoring
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.
Pattern 6: Build Verification
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.
Pattern 7: Permission Confirmation
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.
Pattern 8: Code Quality Checks
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
fi
Use for: Automatic code quality enforcement.
Pattern Combinations
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.
Pattern 9: Temporarily Active Hooks
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-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:
#!/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):
{
"strictMode": true,
"maxFileSize": 500000,
"allowedPaths": ["/tmp", "/home/user/projects"]
}
Use for:
- User-configurable hook behavior
- Per-project settings
- Team-specific rules
- Dynamic validation criteria
Pattern 11: Teleport Workflow Automation
Automate setup when teleporting sessions between web and CLI:
Pre-teleport hook (prepare for transfer):
{
"PreTeleport": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pre-teleport.sh"
}
]
}
]
}
pre-teleport.sh:
#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 0
# Stash uncommitted changes before teleporting
if [ -d ".git" ] && [ -n "$(git status --porcelain)" ]; then
echo "📦 Stashing uncommitted changes..."
git stash push -m "pre-teleport-$(date +%s)"
fi
# Save current state for restoration
echo "$(git branch --show-current)" > .claude/.teleport-state
Post-teleport hook (set up after transfer):
{
"PostTeleport": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/post-teleport.sh"
}
]
}
]
}
post-teleport.sh:
#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 0
# Pull latest changes
if [ -d ".git" ]; then
echo "🔄 Pulling latest changes..."
git pull origin "$(git branch --show-current)" 2>/dev/null || true
# Restore stashed changes if any
if git stash list | grep -q "pre-teleport"; then
echo "📦 Restoring stashed changes..."
git stash pop
fi
fi
# Install dependencies
if [ -f "package.json" ]; then
echo "📦 Installing dependencies..."
npm install --silent
fi
# Start dev server
if [ -f "package.json" ] && grep -q '"dev:staging"' package.json; then
echo "🚀 Starting staging dev server..."
npm run dev:staging &
elif [ -f "package.json" ] && grep -q '"dev"' package.json; then
echo "🚀 Starting dev server..."
npm run dev &
fi
echo "✅ Teleport complete! Environment ready."
Use for:
- Seamless web-to-CLI workflow transitions
- Automatic dev server startup after teleporting
- Preserving uncommitted work across teleports
- Environment setup automation