Help us improve
Share bugs, ideas, or general feedback.
From kookr-toolkit
Claude Code hook system — all 25 event types, payloads, matchers, state machine, and integration patterns for agent monitoring
npx claudepluginhub kookr-ai/kookr --plugin kookr-toolkitHow this skill is triggered — by the user, by Claude, or both
Slash command
/kookr-toolkit:claude-code-hooksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Empirically validated knowledge about Claude Code's hook system, distilled from PoC 001 and PoC 002.
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
Empirically validated knowledge about Claude Code's hook system, distilled from PoC 001 and PoC 002.
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
SessionStart | source: startup, resume, clear, compact | No | source, model |
InstructionsLoaded | load_reason: session_start, nested_traversal, path_glob_match, include, compact | No | file_path, memory_type (User/Project), load_reason |
SessionEnd | (none) | No | reason: clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
UserPromptSubmit | (none) | Yes (exit 2 blocks) | prompt, permission_mode |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
PreToolUse | tool name | Yes (permissionDecision) | tool_name, tool_input, tool_use_id |
PostToolUse | tool name | Partial | tool_name, tool_input, tool_use_id, tool_response |
PostToolUseFailure | tool name | No | tool_name, tool_input, tool_use_id, error |
PermissionRequest | tool name | Yes (behavior) | tool_name, tool_input, permission_suggestions, permission_mode |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
Stop | (none) | Yes (decision: "block") | stop_hook_active, last_assistant_message, permission_mode |
StopFailure | error type | No | error: rate_limit, authentication_failed, billing_error, invalid_request, server_error, max_output_tokens, unknown; last_assistant_message |
Notification | (none — fires for all types) | No | notification_type: idle_prompt, permission_prompt, auth_success, elicitation_dialog; message |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
SubagentStart | agent type | No | agent type name |
SubagentStop | agent type | Yes | last_assistant_message, agent_transcript_path |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
TaskCreated | (none) | Yes (exit 2) | task info |
TaskCompleted | (none) | Yes (exit 2) | task info |
TeammateIdle | (none) | Yes (exit 2 sends feedback) | teammate_name, team_name |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
PreCompact | trigger: manual, auto | No | compaction trigger |
PostCompact | trigger: manual, auto | No | compaction trigger |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
ConfigChange | config source | Yes | source: user_settings, project_settings, local_settings, policy_settings, skills |
CwdChanged | (none) | No | new cwd |
FileChanged | filename (basename) | No | changed filename |
WorktreeCreate | (none) | Yes (non-zero exit fails) | worktree info |
WorktreeRemove | (none) | No | worktree info |
| Event | Matcher | Blocking | Payload fields |
|---|---|---|---|
Elicitation | MCP server name | Yes (action) | MCP server, input request |
ElicitationResult | MCP server name | Yes | user response |
{
"session_id": "uuid-string",
"transcript_path": "/absolute/path/to/session.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "EventName",
"permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions"
}
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest): matcher is tool name, use "*" for all toolsSessionStart: matcher is source field value, use "*" for all sourcesStop, StopFailure, Notification, UserPromptSubmit, SessionEnd, TaskCreated, TaskCompleted, TeammateIdle, CwdChanged): use "" (empty string)InstructionsLoaded: matcher is load_reasonSubagentStart/SubagentStop: matcher is agent type namePreCompact/PostCompact: matcher is trigger typeConfigChange: matcher is config sourceFileChanged: matcher is filename basenameElicitation/ElicitationResult: matcher is MCP server nameif field (v2.1.85+): permission-rule syntax for argument filtering, e.g. "if": "Bash(git *)". Only for tool events.SessionStart ──► INITIALIZING
│
InstructionsLoaded (1..N)
│
UserPromptSubmit ──► ACTIVE ◄─── UserPromptSubmit (from IDLE)
│
┌──────┴──────┐
PreToolUse (thinking)
│ │
TOOL_RUNNING Stop ──► IDLE
│ │
PostToolUse Notification(idle_prompt)
│ │
ACTIVE CONFIRMED_IDLE
│
UserPromptSubmit ──► ACTIVE
(or SessionEnd ──► TERMINATED)
StopFailure ──► ERRORED (from any active state)
PermissionRequest ──► BLOCKED (from ACTIVE/TOOL_RUNNING)
SessionEnd ──► TERMINATED (from any state)
Stop — immediate "finished turn" signal (soft idle)Notification(idle_prompt) — fires ~60s later (confirmed idle, developer hasn't responded)Use Stop for "needs_input" (info). Use Notification(idle_prompt) for escalation or auto-proceed.
UserPromptSubmit is the definitive signal. Clears all idle-related anomalies.
| Type | Description | Default timeout |
|---|---|---|
command | Shell command, JSON on stdin | 600s |
http | POST to URL endpoint | 30s |
prompt | Single-turn LLM evaluation | 30s |
agent | Multi-turn subagent with tools | 60s |
| Location | Scope |
|---|---|
~/.claude/settings.json | User-global |
.claude/settings.json | Project (committable) |
.claude/settings.local.json | Project (gitignored) |
--settings <file> flag | Per-launch (additive to above) |
Hooks from --settings are additive — they don't replace user or project hooks.
When you're building a supervisor that spawns Claude Code child agents and wants to capture every hook event for monitoring, generate a per-agent --settings <file> with all hook events wired to a single dispatcher command. The dispatcher can dual-write (e.g. append-to-JSONL for replay AND HTTP POST for live UI updates):
// Hooks that match tool names use "*"
// Hooks without matcher semantics use ""
const TOOL_MATCHER = '*';
const NO_MATCHER = '';
const hookEntries = {
SessionStart: [{ matcher: TOOL_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
PreToolUse: [{ matcher: TOOL_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
PostToolUse: [{ matcher: TOOL_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
PostToolUseFailure: [{ matcher: TOOL_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
Stop: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
StopFailure: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
PermissionRequest: [{ matcher: TOOL_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
Notification: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
UserPromptSubmit: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
SubagentStart: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
SubagentStop: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
SessionEnd: [{ matcher: NO_MATCHER, hooks: [{ type: 'command', command: hookCmd }] }],
};
All payloads and event names documented above were captured from live sessions (Claude Code v2.1.81–v2.1.87), not from the docs. Heuristic for any future event: spawn a session with a hook wired to every documented event and cat the stdin payload it actually receives — the binary is the source of truth.
These strings appear in the binary but are NOT hook events:
PostToolUseFailure hookThe binary validates hook names at startup — invalid names cause a settings error dialog. The definitive enum has exactly 26 valid hook event names.