creating-hooks
STARTER_CHARACTER = 🪝
Setup
First, update the reference docs to get the latest from Anthropic:
python ~/.claude/skills/creating-hooks/scripts/update-docs.py
What Hooks Are
Shell commands that execute at lifecycle points in Claude Code. Unlike prompts, hooks are deterministic—they always run when triggered.
Configuration
Hooks live in settings files:
~/.claude/settings.json- User settings (all projects).claude/settings.json- Project settings (shared via git).claude/settings.local.json- Local project settings (not committed)
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here"
}
]
}
]
}
}
Matcher: Pattern to match tool names (case-sensitive)
- Exact match:
Write - Regex:
Edit|Write - All tools:
*or omit
Environment variables:
$CLAUDE_PROJECT_DIR- Absolute path to project root$CLAUDE_ENV_FILE- File path for persisting env vars (SessionStart only)
Hook Events
Tool events (matcher applies):
PreToolUse- Before tool executesPostToolUse- After tool completesPermissionRequest- Permission dialog shown
Session events:
SessionStart- Session begins/resumes (matcher: startup/resume/clear/compact)SessionEnd- Session endsPreCompact- Before compaction (matcher: manual/auto)
Other events:
UserPromptSubmit- User submits promptStop- Agent finishesSubagentStop- Subagent finishesNotification- Alerts sent (matcher: notification type)
Exit Codes
- 0: Success. stdout shown in verbose mode. For
UserPromptSubmit/SessionStart, stdout added to context. - 2: Block. stderr fed to Claude as error message. Blocks the action.
- Other: Non-blocking error. stderr shown to user.
JSON Output
For advanced control, return JSON to stdout with exit code 0:
{
"continue": false,
"stopReason": "Message shown when stopping"
}
PreToolUse Control
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved",
"updatedInput": { "field": "modified value" }
}
}
Decisions: "allow" (bypass permission), "deny" (block), "ask" (prompt user)
PostToolUse Feedback
{
"decision": "block",
"reason": "Explanation fed to Claude"
}
Stop/SubagentStop Control
{
"decision": "block",
"reason": "Must fix X before stopping"
}
Hook Input
Hooks receive JSON via stdin:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/dir",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": { "file_path": "/path", "content": "..." }
}
Common Patterns
Auto-format after edit:
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -I{} sh -c 'echo {} | grep -q \"\\.ts$\" && npx prettier --write {}'"
}]
}]
}
}
Block dangerous commands:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.py"
}]
}]
}
}
Inject context on prompt:
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "echo '[REMINDER: Follow TDD]'"
}]
}]
}
}
Desktop notification:
{
"hooks": {
"Notification": [{
"hooks": [{
"type": "command",
"command": "osascript -e 'display notification \"Claude needs input\" with title \"Claude Code\"'"
}]
}]
}
}
Hook Scripts
For complex logic, use external scripts. UV single-file format works well:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = []
# requires-python = ">=3.11"
# ///
import json
import sys
data = json.load(sys.stdin)
tool_input = data.get("tool_input", {})
# Validation logic here
if should_block:
print("Error message", file=sys.stderr)
sys.exit(2)
sys.exit(0)
Anti-Patterns
- Using exit code 2 without stderr message (Claude gets no feedback)
- Forgetting to handle JSON parsing errors in scripts
- Blocking without explaining why (Claude will retry the same thing)
- Long-running hooks without timeout (default is 60s)
- Modifying files in PreToolUse (use PostToolUse for modifications)
Reference
- references/anthropic-hooks.md - Complete reference (input schemas, prompt hooks, MCP tools)
- references/anthropic-hooks-guide.md - Quickstart and examples
More from lexler/skill-factory
hexagonal-architecture
Applies hexagonal (ports & adapters) architecture. Use when designing application structure, separating domain from infrastructure, creating testable boundaries, or when user mentions ports, adapters, hexagonal, or clean architecture.
13using-uv
Python package and project management with UV. Use when creating Python scripts, initializing projects, or managing dependencies.
5approval-tests
Writes approval tests (snapshot/golden master testing) for Python, JavaScript/TypeScript, or Java. Use when verifying complex output, characterization testing legacy code, testing combinations, or working with .approved/.received files.
5refactoring
Refactoring process. Invoke immediately when user or document mentions refactoring, or proactively when code gets too complex or messy.
5git-worktrees
Creates git worktrees for parallel development. Use when creating a git worktree, setting up multiple working directories, or working on features in parallel.
5nullables
Writes tests without mocks using Nullables. Use when writing tests, especially testing code with external I/O (HTTP, files, databases, clocks, random numbers), designing infrastructure wrappers or replacing mocking libraries.
5