- name
- hook-developer
- description
- Complete Claude Code hooks reference - input/output schemas, registration, testing patterns
# 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:**
```json
{
"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):**
```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:**
```json
{
"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):**
```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:**
```json
{
"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):**
```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:**
```json
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PermissionRequest",
"tool_name": "string",
"tool_input": {}
}
```
**Output:**
```json
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "string",
"interrupt": false
}
}
}
```
---
### SessionStart
**Purpose:** Initialize session, load context, set environment variables.
**Input:**
```json
{
"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):**
```json
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "string"
},
"suppressOutput": true
}
```
Plain text stdout is added as context.
---
### SessionEnd
**Purpose:** Cleanup, save state, log session.
**Input:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"continue": true,
"systemMessage": "string"
}
```
---
### Notification
**Purpose:** Custom notification handling.
**Input:**
```json
{
"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:**
```json
{
"continue": true,
"suppressOutput": true,
"systemMessage": "string"
}
```
---
## Registration in settings.json
### Standard Structure
```json
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
Ver en GitHub