Hook Development Guide
Create Claude Code hooks that intercept events in the lifecycle and can validate, modify, or block operations.
Hook Types
- Command Hooks (recommended) -- Execute a script for deterministic checks
- Prompt Hooks -- Use LLM reasoning for context-aware decisions
- Agent Hooks (v2.1.0+) -- Leverage agent capabilities for complex workflows
Hook Events
| Event | Trigger | Common Uses |
|---|---|---|
PreToolUse |
Before any tool executes | Block dangerous commands, validate inputs |
PostToolUse |
After tool completes | Format code, run linters, log results |
SessionStart |
When session begins | Check environment, load config |
SessionEnd |
When session ends | Cleanup, save state |
Stop |
When agent stops | Verify task completion |
SubagentStop |
When subagent stops | Validate subagent work |
UserPromptSubmit |
When user sends message | Process user input |
PreCompact |
Before context compression | Preserve critical info |
Notification |
System notifications | React to events |
PermissionRequest |
Permission dialogs (v2.1.0) | Custom permission handling |
Configuration
hooks.json Format
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-bash.py",
"timeout": 10
}
]
}
]
}
}
Matchers: "Bash", "Edit", "Read" (case-sensitive tool names), or "*" for all.
Environment Variables
| Variable | Description |
|---|---|
CLAUDE_PLUGIN_ROOT |
Plugin directory (always use for portable paths) |
CLAUDE_PROJECT_DIR |
Current project root |
CLAUDE_ENV_FILE |
Persist variables from SessionStart |
Writing Command Hooks
Copy and customize a template from templates/:
| Template | Purpose |
|---|---|
templates/pretooluse-bash.py |
PreToolUse hook for Bash commands |
templates/pretooluse-read.py |
PreToolUse hook for file reads |
templates/posttooluse-edit.py |
PostToolUse hook for formatting |
templates/sessionstart.sh |
SessionStart initialization |
cp ${CLAUDE_PLUGIN_ROOT}/skills/create-hook/templates/pretooluse-bash.py \
plugins/my-hook/hooks/my-hook.py
chmod +x plugins/my-hook/hooks/my-hook.py
Essential Patterns
Always check safe patterns before blocking (allowlist-first):
SAFE_PATTERNS = [r"rm\s+-rf\s+/tmp/"]
BLOCKED_PATTERNS = [(r"rm\s+-rf\s+", "rm -rf is destructive")]
for pattern in SAFE_PATTERNS:
if re.search(pattern, command):
c.output.exit_success()
for pattern, reason in BLOCKED_PATTERNS:
if re.search(pattern, command):
c.output.exit_block(reason)
Exit methods:
c.output.exit_success()-- allow operationc.output.exit_block("reason")-- block with messagec.output.exit_modify({"command": modified})-- modify input (PreToolUse only)
Early exit when hook doesn't apply:
if c.tool_name != "Bash":
c.output.exit_success()
Creating a New Hook Plugin
Create directory structure:
mkdir -p plugins/my-hook/.claude-plugin plugins/my-hook/hooksCreate
plugins/my-hook/.claude-plugin/plugin.json:{"name": "my-hook", "version": "1.0.0", "description": "What this hook does"}Create
plugins/my-hook/hooks/hooks.jsonwith event matchers (see Configuration above)Copy and customize a template from this skill's
templates/directoryValidate:
claude plugin validate .
Debugging
Test hooks directly by piping JSON input:
echo '{"tool_name": "Bash", "tool_input": {"command": "rm -rf /"}}' | \
python plugins/my-hook/hooks/my-hook.py
Validate hooks.json: cat plugins/my-hook/hooks/hooks.json | jq .
Reference Examples
Existing hooks in this repository:
- Block destructive commands:
plugins/guards/security/safety-guard/ - Enforce commit format:
plugins/guards/policy/conventional-commits/ - Format on save:
plugins/guards/quality/python-format/ - Protect sensitive files:
plugins/guards/security/protect-env/