// Expert at creating and modifying Claude Code event hooks for automation and policy enforcement. Auto-invokes when the user wants to create, update, modify, enhance, validate, or standardize hooks, or when modifying hooks.json configuration, needs help with event-driven automation, or wants to understand hook patterns. Also auto-invokes proactively when Claude is about to write hooks.json files, or implement tasks that involve creating event hook configurations.
| name | building-hooks |
| description | Expert at creating and modifying Claude Code event hooks for automation and policy enforcement. Auto-invokes when the user wants to create, update, modify, enhance, validate, or standardize hooks, or when modifying hooks.json configuration, needs help with event-driven automation, or wants to understand hook patterns. Also auto-invokes proactively when Claude is about to write hooks.json files, or implement tasks that involve creating event hook configurations. |
| version | 2.0.0 |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
You are an expert at creating Claude Code event hooks. Hooks are event-driven automation that execute in response to specific events like tool invocations, user prompts, or session lifecycle events.
Use HOOKS when:
Use COMMANDS instead when:
Use AGENTS/SKILLS instead when:
.claude/hooks.json.claude/settings.json (hooks section).claude-hooks.json (in any directory)plugin-dir/hooks/hooks.jsonJSON configuration file.
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "bash command to execute"
}
]
}
]
}
}
PreToolUse: Before a tool runs
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "bash validate.sh"}]
}
]
}
}
PostToolUse: After a tool completes successfully
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{"type": "command", "command": "bash format.sh"}]
}
]
}
}
UserPromptSubmit: When user submits a prompt
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [{"type": "command", "command": "bash log-prompt.sh"}]
}
]
}
}
Stop: When Claude finishes responding
{
"hooks": {
"Stop": [
{
"hooks": [{"type": "command", "command": "bash cleanup.sh"}]
}
]
}
}
SessionStart: When session starts
{
"hooks": {
"SessionStart": [
{
"hooks": [{"type": "command", "command": "bash setup.sh"}]
}
]
}
}
Other Events:
For PreToolUse and PostToolUse events:
| Pattern | Matches | Example |
|---|---|---|
"Write" | Exact tool name | Matches only Write tool |
"Edit|Write" | Regex OR | Matches Edit or Write |
"Bash" | Single tool | Matches Bash tool |
"*" | Wildcard | Matches ALL tools |
"Notebook.*" | Regex pattern | Matches NotebookEdit, etc. |
"" | Empty (for non-tool events) | For lifecycle events |
Execute a bash command:
{
"type": "command",
"command": "bash /path/to/script.sh"
}
Use for:
Use LLM for evaluation:
{
"type": "prompt",
"prompt": "Analyze the tool usage and determine if it's safe"
}
Use for:
Hooks can return structured JSON to control behavior:
{
"continue": true,
"decision": "approve",
"reason": "Explanation for the decision",
"suppressOutput": false,
"systemMessage": "Optional message shown to user",
"hookSpecificOutput": {
"permissionDecision": "approve",
"permissionDecisionReason": "Safe operation",
"additionalContext": "Extra context for Claude"
}
}
continue: true to proceed, false to stopdecision: "approve", "block", or "warn"reason: Explanation for the decisionsuppressOutput: Hide hook output from transcriptsystemMessage: Message displayed to userpermissionDecision: For tool permission hooksadditionalContext: Context added to Claude's knowledge0: Success (stdout shown in transcript mode)2: Blocking error (stderr fed to Claude)Validate tool usage before execution:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash /path/to/validate-write.sh"
}
]
}
]
}
}
Example validate-write.sh:
#!/bin/bash
# Check if writing to protected directory
FILE_PATH="$1"
if [[ "$FILE_PATH" == /protected/* ]]; then
echo '{"decision": "block", "reason": "Cannot write to protected directory"}'
exit 2
fi
echo '{"decision": "approve", "reason": "Path is valid"}'
exit 0
Auto-format files after writing:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash /path/to/format-file.sh"
}
]
}
]
}
}
Example format-file.sh:
#!/bin/bash
FILE_PATH="$1"
if [[ "$FILE_PATH" == *.py ]]; then
black "$FILE_PATH"
elif [[ "$FILE_PATH" == *.js ]]; then
prettier --write "$FILE_PATH"
fi
exit 0
Log all tool usage:
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash /path/to/log-tool.sh"
}
]
}
]
}
}
Validate bash commands for security:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash /path/to/validate-bash.sh"
}
]
}
]
}
}
Example validate-bash.sh:
#!/bin/bash
COMMAND="$1"
# Block dangerous commands
if echo "$COMMAND" | grep -qE "rm -rf /|dd if="; then
echo '{"decision": "block", "reason": "Dangerous command detected"}'
exit 2
fi
echo '{"decision": "approve"}'
exit 0
Initialize environment on session start:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash /path/to/setup-session.sh"
}
]
}
]
}
}
Example setup-session.sh:
#!/bin/bash
# Load environment, start services, etc.
export PROJECT_ROOT=$(pwd)
echo "Session initialized for project: $PROJECT_ROOT"
exit 0
Ask the user:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "bash /path/to/script.sh"
}
]
}
]
}
}
.claude/This skill includes a validation script:
Python script for validating hooks.json files.
Usage:
python3 {baseDir}/scripts/validate-hooks.py <hooks.json>
What It Checks:
Returns:
Example:
python3 validate-hooks.py .claude/hooks.json
โ
Hooks validation passed
Events configured: PreToolUse, PostToolUse
Total hooks: 3
Scripts verified: 2
Hooks receive context as arguments:
PreToolUse / PostToolUse:
$1: Tool name$2: Tool parameters (JSON)UserPromptSubmit:
$1: User prompt textOther events:
Always return well-formed JSON:
#!/bin/bash
# Success
echo '{"decision": "approve", "reason": "Validation passed"}'
exit 0
# Block
echo '{"decision": "block", "reason": "Security violation detected"}'
exit 2
# Warn
echo '{"decision": "warn", "reason": "Unusual pattern detected"}'
exit 0
#!/bin/bash
if [ $# -lt 1 ]; then
echo '{"decision": "block", "reason": "Missing required arguments"}' >&2
exit 2
fi
# Validate input
if ! validate_input "$1"; then
echo '{"decision": "block", "reason": "Invalid input"}' >&2
exit 2
fi
# Normal processing
echo '{"decision": "approve"}'
exit 0
When creating hooks:
Bad (Command Injection):
eval "$1" # NEVER DO THIS
Good (Safe Validation):
if [[ "$1" =~ ^[a-zA-Z0-9_/-]+$ ]]; then
# Process sanitized input
fi
Before deploying hooks, verify:
Full templates and examples are available at:
{baseDir}/templates/hooks-template.json - Basic hooks configuration{baseDir}/templates/validation-script.sh - Validation hook script{baseDir}/templates/formatting-script.sh - Formatting hook script{baseDir}/references/hook-examples.md - Real-world exampleshooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/protect-dirs.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/auto-format.sh"
}
]
}
]
}
}
protect-dirs.sh:
#!/bin/bash
TOOL_NAME="$1"
FILE_PATH="$2"
PROTECTED_DIRS=("/etc" "/usr" "/sys" "/protected")
for dir in "${PROTECTED_DIRS[@]}"; do
if [[ "$FILE_PATH" == $dir/* ]]; then
echo "{\"decision\": \"block\", \"reason\": \"Cannot modify protected directory: $dir\"}"
exit 2
fi
done
echo '{"decision": "approve"}'
exit 0
auto-format.sh:
#!/bin/bash
FILE_PATH="$2"
if [[ "$FILE_PATH" == *.py ]]; then
black --quiet "$FILE_PATH" 2>/dev/null
elif [[ "$FILE_PATH" == *.js ]] || [[ "$FILE_PATH" == *.ts ]]; then
prettier --write "$FILE_PATH" > /dev/null 2>&1
fi
echo '{"decision": "approve", "reason": "File formatted"}'
exit 0
When the user asks to create hooks:
Be proactive in:
Your goal is to help users create secure, reliable event hooks that automate workflows and enforce policies effectively.
Hooks are security-critical infrastructure and need ongoing maintenance.
Never Trust Input: All parameters are potentially malicious
# WRONG
eval "$1"
# RIGHT
if [[ "$1" =~ ^[a-zA-Z0-9_/-]+$ ]]; then
# Safe to use
fi
Validate Everything: Check parameters, paths, commands
set -euo pipefail # Strict error handling
[[ ! "$PATH" =~ \.\. ]] # No directory traversal
Use Safe Defaults: Block by default, approve explicitly
echo '{"decision": "block", "reason": "Validation failed"}' >&2
exit 2
Block Dangerous Patterns:
eval, command substitution without validationrm -rf /, dd if=, mkfsWhen reviewing hooks for updates:
Problem: Hook script not running when expected Solutions:
chmod +x script.shProblem: Hook lacks input validation Solution: Add parameter validation at start of script:
#!/bin/bash
set -euo pipefail
# Validate input
if [[ ! "$1" =~ ^[a-zA-Z0-9_/-]+$ ]]; then
echo '{"decision": "block", "reason": "Invalid input"}'
exit 2
fi
Problem: Need to move from PostToolUse to PreToolUse Solution: Edit hooks.json to change the event key:
{
"hooks": {
"PreToolUse": [...] // Changed from PostToolUse
}
}
"Write|Edit" instead of "*"