creating-hooks
About
This skill provides a comprehensive guide for implementing Claude Code hooks to create event-driven automation. It covers all hook events, matchers, exit codes, and environment variables for use cases like auto-linting, validation, and context injection. Developers should use it when building automation that triggers at specific workflow points like PreToolUse or PostToolUse.
Quick Install
Claude Code
Recommended/plugin add https://github.com/majiayu000/claude-skill-registrygit clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/creating-hooksCopy and paste this command in Claude Code to install this skill
Documentation
Creating Hooks
Build event-driven automation for Claude Code using hooks - scripts that execute at specific workflow points.
Quick Reference
| Hook Event | When It Fires | Uses Matcher | Common Use Cases |
|---|---|---|---|
PreToolUse | Before tool executes | Yes (tool name) | Validation, auto-approval, input modification |
PostToolUse | After tool succeeds | Yes (tool name) | Auto-formatting, linting, logging |
PostToolUseFailure | After tool fails | Yes (tool name) | Error handling, fallback logic |
PermissionRequest | User shown permission dialog | Yes (tool name) | Auto-allow/deny, policy enforcement |
Notification | Claude sends notification | Yes (type) | Custom alerts, logging |
UserPromptSubmit | User submits prompt | No | Prompt validation, context injection |
Setup | --init or --maintenance | Yes (trigger) | Dependency install, migrations, cleanup |
Stop | Main agent finishes | No | Task completion checks, force continue |
SubagentStart | Subagent (Task) spawns | No | Logging, tracking, rate limiting |
SubagentStop | Subagent (Task) finishes | No | Subagent task validation |
PreCompact | Before context compaction | Yes (trigger) | Custom compaction handling |
SessionStart | Session begins/resumes | Yes (source) | Context loading, env setup |
SessionEnd | Session ends | Yes (reason) | Cleanup, logging |
Updated for Claude Code 2.1.17
Configuration Locations
Hooks are configured in settings files (in order of precedence):
| Location | Scope | Committed |
|---|---|---|
~/.claude/settings.json | User (all projects) | No |
.claude/settings.json | Project | Yes |
.claude/settings.local.json | Local project | No |
| Enterprise managed policy | Organization | Yes |
Hook Structure
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 30
}
]
}
]
}
}
Matcher Syntax
| Pattern | Matches | Example |
|---|---|---|
Write | Exact tool name | Only Write tool |
Edit|Write | Regex OR | Edit or Write |
Notebook.* | Regex wildcard | NotebookEdit, NotebookRead |
mcp__memory__.* | MCP server tools | All memory server tools |
* or "" | All tools | Any tool |
Note: Matchers are case-sensitive and only apply to PreToolUse, PostToolUse, and PermissionRequest.
Hook Types
| Type | Description | Key Field |
|---|---|---|
command | Execute bash script | command: bash command to run |
prompt | LLM-based evaluation | prompt: prompt text for Haiku |
Hook Options
| Option | Type | Description |
|---|---|---|
timeout | number | Timeout in seconds (default: 60, max: 600 as of 2.1.3) |
once | boolean | Run only once per session (frontmatter hooks only) |
Note: As of 2.1.3, the maximum hook timeout was increased from 60 seconds to 10 minutes (600s).
Exit Codes
| Exit Code | Meaning | Behavior |
|---|---|---|
0 | Success | Continue normally. stdout parsed for JSON control |
2 | Blocking error | Block action. stderr shown to Claude |
| Other | Non-blocking error | Log warning. Continue normally |
Exit Code 2 Behavior by Event
| Event | Exit Code 2 Effect |
|---|---|
PreToolUse | Blocks tool call, stderr to Claude |
PermissionRequest | Denies permission, stderr to Claude |
PostToolUse | stderr to Claude (tool already ran) |
UserPromptSubmit | Blocks prompt, erases it, stderr to user |
Stop / SubagentStop | Blocks stoppage, stderr to Claude |
Notification / SessionStart / SessionEnd / PreCompact | stderr to user only |
Environment Variables
| Variable | Description | Available In |
|---|---|---|
CLAUDE_PROJECT_DIR | Absolute path to project root | All hooks |
CLAUDE_PLUGIN_ROOT | Absolute path to plugin directory | Plugin hooks only |
CLAUDE_ENV_FILE | File path for persisting env vars | SessionStart only |
CLAUDE_CODE_REMOTE | "true" if running in web environment | All hooks |
Hook Input (stdin)
All hooks receive JSON via stdin with common fields:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/directory",
"permission_mode": "default",
"hook_event_name": "EventName"
}
Permission modes: default, plan, acceptEdits, dontAsk, bypassPermissions
Decision Guide: Which Hook Do I Need?
Before Tool Execution
Use PreToolUse to:
- Validate tool inputs before execution
- Auto-approve safe operations (e.g., reading docs)
- Block dangerous commands
- Modify tool inputs
After Tool Execution
Use PostToolUse to:
- Auto-format code after Write/Edit
- Run linters after file changes
- Log file modifications
- Provide feedback to Claude
Permission Automation
Use PermissionRequest to:
- Auto-allow trusted operations
- Auto-deny blocked patterns
- Enforce security policies
Prompt Processing
Use UserPromptSubmit to:
- Inject context (current time, git status)
- Validate prompts for secrets
- Block sensitive requests
Session Lifecycle
Use SessionStart to:
- Load development context
- Set environment variables
- Install dependencies
Use SessionEnd to:
- Clean up resources
- Log session statistics
Agent Completion
Use Stop / SubagentStop to:
- Verify task completion
- Force Claude to continue working
- Add completion checks
Context Management
Use PreCompact to:
- Customize compaction behavior
- Add pre-compaction context
Alerts
Use Notification to:
- Custom notification routing
- Third-party integrations (Slack, Discord)
Workflow: Creating a Hook
Prerequisites
- Identify which event to hook into
- Decide: command (bash) or prompt (LLM) type
- Plan exit code behavior
Steps
-
Create hook script
- Write executable script (bash, python, etc.)
- Read JSON from stdin
- Output JSON to stdout (if needed)
- Use appropriate exit code
-
Configure in settings
- Add to appropriate settings file
- Set matcher pattern (if applicable)
- Set timeout if needed (default: 60s)
-
Test
- Run
claude --debugto see hook execution - Check
/hooksmenu for registration - Verify exit codes work as expected
- Run
Validation
- Script is executable (
chmod +x) - JSON input/output is valid
- Exit codes are correct
- Matcher pattern works
Tool-Specific Hooks
Common patterns for hooks targeting specific tools.
Bash Tool Hooks
Validate commands before execution, log sensitive operations, or block dangerous commands.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.sh"
}
]
}
]
}
}
Common validations:
- Block
rm -rf /patterns - Require approval for
sudocommands - Log all commands to audit file
- Block network commands in certain contexts
Write Tool Hooks
Validate file paths, enforce naming conventions, or auto-format after write.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/after-write.sh"
}
]
}
]
}
}
Common patterns:
- Auto-format with Prettier/Black
- Validate file encoding (UTF-8)
- Check for accidental credential writes
- Run type-checking after TypeScript writes
Edit Tool Hooks
Validate edits, prevent changes to critical files, or run linting after edits.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-edit.sh"
}
]
}
]
}
}
Common patterns:
- Block edits to lock files (package-lock.json)
- Prevent edits to generated files
- Run linter after file edits
- Validate imports/exports after module changes
Read Tool Hooks
Log file access, validate read permissions, or inject context based on files read.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-read.sh"
}
]
}
]
}
}
Common patterns:
- Block reading sensitive files (.env, credentials)
- Log file access for auditing
- Auto-approve reading documentation
- Inject related context when reading specific files
Common Patterns
Auto-Format on File Write
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh"
}
]
}
]
}
}
Inject Context on Session Start
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Git branch: $(git branch --show-current)\""
}
]
}
]
}
}
Auto-Approve Documentation Reads
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/approve-docs.py"
}
]
}
]
}
}
Hook Framework (YAML Configuration)
For projects with multiple hooks, the Hook Framework provides a YAML-based configuration with built-in handlers and environment variable injection.
Installation
bun add claude-code-sdk
YAML Configuration
Create hooks.yaml in your project root:
version: 1
settings:
debug: false
parallelExecution: true
defaultTimeoutMs: 30000
builtins:
# Human-friendly session names (e.g., "brave-elephant")
session-naming:
enabled: true
options:
format: adjective-animal
# Track turns between Stop events
turn-tracker:
enabled: true
# Block dangerous Bash commands
dangerous-command-guard:
enabled: true
options:
blockedPatterns:
- "rm -rf /"
- "rm -rf ~"
# Inject session context
context-injection:
enabled: true
options:
template: "Session: ${sessionName} | Turn: ${turnId}"
# Log tool usage
tool-logger:
enabled: true
options:
outputPath: ~/.claude/logs/tools.log
handlers:
# Custom command handlers
my-validator:
events: [PreToolUse]
matcher: "Bash"
command: ./scripts/validate-command.sh
timeoutMs: 5000
Built-in Handlers
| Handler | Description | Default Events |
|---|---|---|
session-naming | Assigns human-friendly names | SessionStart |
turn-tracker | Tracks turns between Stop events | SessionStart, Stop, SubagentStop |
dangerous-command-guard | Blocks dangerous Bash commands | PreToolUse |
context-injection | Injects session/turn context | SessionStart, PreCompact |
tool-logger | Logs tool usage with context | PostToolUse |
event-logger | Logs all hook events to JSONL for indexing | All events |
debug-logger | Full payload logging for debugging | All events |
metrics | Records hook execution timing metrics | All events |
Environment Variables for Custom Handlers
Custom command handlers receive these environment variables:
| Variable | Description |
|---|---|
CLAUDE_SESSION_ID | Current session ID |
CLAUDE_SESSION_NAME | Human-friendly session name |
CLAUDE_TURN_ID | Turn identifier (session:sequence) |
CLAUDE_TURN_SEQUENCE | Current turn number |
CLAUDE_EVENT_TYPE | Hook event type |
CLAUDE_CWD | Current working directory |
CLAUDE_PROJECT_DIR | Project root path |
TypeScript Framework
import { createFramework, handler, blockResult } from 'claude-code-sdk/hooks/framework';
const framework = createFramework({ debug: true });
// Block dangerous commands
framework.onPreToolUse(
handler()
.id('danger-guard')
.forTools('Bash')
.handle(ctx => {
const input = ctx.event.tool_input as { command?: string };
if (input.command?.includes('rm -rf /')) {
return blockResult('Dangerous command blocked');
}
return { success: true };
})
);
// Access turn/session context
framework.onPostToolUse(
handler()
.id('context-logger')
.handle(ctx => {
const turnId = ctx.results.get('turn-tracker')?.data?.turnId;
const sessionName = ctx.results.get('session-naming')?.data?.sessionName;
console.error(`[${sessionName}] Turn ${turnId}: ${ctx.event.tool_name}`);
return { success: true };
})
);
await framework.run();
Using with settings.json
Point your settings.json to the framework entry point:
{
"hooks": {
"PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
"PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
"SessionStart": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }],
"Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bun run hooks-framework" }] }]
}
}
Debugging
| Issue | Solution |
|---|---|
| Hook not running | Check /hooks menu, verify JSON syntax |
| Wrong matcher | Tool names are case-sensitive |
| Command not found | Use absolute paths or $CLAUDE_PROJECT_DIR |
| Script not executing | Check permissions (chmod +x) |
| Exit code ignored | Only 0, 2, and other are recognized |
| Framework not loading | Check hooks.yaml syntax, run with debug: true |
Run with debug mode:
claude --debug
Security Considerations
- Validate and sanitize all inputs
- Quote shell variables (
"$VAR"not$VAR) - Check for path traversal (
..) - Use absolute paths for scripts
- Skip sensitive files (
.env, keys)
Frontmatter Hooks
Hooks can also be defined directly in YAML frontmatter of Skills, Agents, and Slash Commands. These hooks are:
- Lifecycle-scoped - Only active while the component executes
- Auto-cleanup - Removed when the component finishes
- Portable - Packaged with the component for distribution
Supported events: PreToolUse, PostToolUse, Stop
Quick Example (in a Skill)
---
name: my-skill
description: A skill with lifecycle hooks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./validate.sh"
once: true
---
Key Differences from Settings Hooks
| Aspect | Settings Hooks | Frontmatter Hooks |
|---|---|---|
| Location | settings.json | Skill/Agent/Command YAML |
| Scope | Global or project | Component lifecycle |
| Events | All 10 events | PreToolUse, PostToolUse, Stop |
| Cleanup | Manual | Automatic |
once option | No | Yes |
See FRONTMATTER-HOOKS.md for complete documentation.
Reference Files
| File | Contents |
|---|---|
| EVENTS.md | Detailed event documentation with input/output schemas |
| EXAMPLES.md | Complete working examples |
| FRONTMATTER-HOOKS.md | Frontmatter hooks in skills, agents, commands |
| TROUBLESHOOTING.md | Common issues and solutions |
GitHub Repository
Related Skills
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
creating-opencode-plugins
MetaThis skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
