Hook Developer
Complete reference for developing Claude Code hooks. Use this to write hooks with correct input/output schemas.
When to Use
- Creating a new hook
- Debugging hook input/output format
- Understanding what fields are available
- Setting up hook registration in settings.json
- Learning what hooks can block vs inject context
Quick Reference
| Hook | Fires When | Can Block? | Primary Use |
|---|---|---|---|
| PreToolUse | Before tool executes | YES | Block/modify tool calls |
| PostToolUse | After tool completes | Partial | React to tool results |
| UserPromptSubmit | User sends prompt | YES | Validate/inject context |
| PermissionRequest | Permission dialog shows | YES | Auto-approve/deny |
| SessionStart | Session begins | NO | Load context, set env vars |
| SessionEnd | Session ends | NO | Cleanup/save state |
| Stop | Agent finishes | YES | Force continuation |
| SubagentStart | Subagent spawns | NO | Pattern coordination |
| SubagentStop | Subagent finishes | YES | Force continuation |
| PreCompact | Before compaction | NO | Save state |
| Notification | Notification sent | NO | Custom alerts |
Hook type options: type: "command" (bash) or type: "prompt" (LLM evaluation)
Hook Input/Output Schemas
PreToolUse
Purpose: Block or modify tool execution before it happens.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "default|plan|acceptEdits|bypassPermissions",
"hook_event_name": "PreToolUse",
"tool_name": "string",
"tool_input": {
"file_path": "string",
"command": "string"
},
"tool_use_id": "string"
}
Output (JSON):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "string",
"updatedInput": {}
},
"continue": true,
"stopReason": "string",
"systemMessage": "string",
"suppressOutput": true
}
Exit code 2: Blocks tool, stderr shown to Claude.
Common matchers: Bash, Edit|Write, Read, Task, mcp__.*
PostToolUse
Purpose: React to tool execution results, provide feedback to Claude.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PostToolUse",
"tool_name": "string",
"tool_input": {},
"tool_response": {
"filePath": "string",
"success": true,
"output": "string",
"exitCode": 0
},
"tool_use_id": "string"
}
CRITICAL: The response field is tool_response, NOT tool_result.
Output (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "string"
},
"continue": true,
"stopReason": "string",
"suppressOutput": true
}
Blocking: "decision": "block" with "reason" prompts Claude to address the issue.
Common matchers: Edit|Write, Bash
UserPromptSubmit
Purpose: Validate user prompts, inject context before Claude processes.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "UserPromptSubmit",
"prompt": "string"
}
Output (Plain text):
Any stdout text is added to context for Claude.
Output (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "string"
}
}
Blocking: "decision": "block" erases prompt, shows "reason" to user only (not Claude).
Exit code 2: Blocks prompt, shows stderr to user only.
PermissionRequest
Purpose: Automate permission dialog decisions.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PermissionRequest",
"tool_name": "string",
"tool_input": {}
}
Output:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "string",
"interrupt": false
}
}
}
SessionStart
Purpose: Initialize session, load context, set environment variables.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionStart",
"source": "startup|resume|clear|compact"
}
Environment variable: CLAUDE_ENV_FILE - write export VAR=value to persist env vars.
Output (Plain text or JSON):
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "string"
},
"suppressOutput": true
}
Plain text stdout is added as context.
SessionEnd
Purpose: Cleanup, save state, log session.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionEnd",
"reason": "clear|logout|prompt_input_exit|other"
}
Output: Cannot affect session (already ending). Use for cleanup only.
Stop
Purpose: Control when Claude stops, force continuation.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Stop",
"stop_hook_active": false
}
CRITICAL: Check stop_hook_active: true to prevent infinite loops!
Output:
{
"decision": "block",
"reason": "string"
}
Blocking: "decision": "block" forces Claude to continue with "reason" as prompt.
SubagentStart
Purpose: Run when a subagent (Task tool) is spawned.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStart",
"agent_id": "string"
}
Output: Context injection only (cannot block).
SubagentStop
Purpose: Control when subagents (Task tool) stop.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStop",
"stop_hook_active": false
}
Output: Same as Stop.
PreCompact
Purpose: Save state before context compaction.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PreCompact",
"trigger": "manual|auto",
"custom_instructions": "string"
}
Matchers: manual, auto
Output:
{
"continue": true,
"systemMessage": "string"
}
Notification
Purpose: Custom notification handling.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Notification",
"message": "string",
"notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}
Matchers: permission_prompt, idle_prompt, auth_success, elicitation_dialog, *
Output:
{
"continue": true,
"suppressOutput": true,
"systemMessage": "string"
}
Registration in settings.json
Standard Structure
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
]
}
]
}
}
Matcher Patterns
| Pattern | Matches |
|---|---|
Bash | Exactly Bash tool |
Edit|Write | Edit OR Write |
Read.* | Regex: Read* |
mcp__.*__write.* | MCP write tools |
| `* |