Claude Code hooks generator
Pick a hook event, a tool matcher, and a command — or start from a recipe like auto-format on save — and copy settings.json that Claude Code accepts.
Fires when right after a tool call succeeds. Exit 2: Shows stderr to Claude — the tool already ran.
- TipUses jq to read the JSON on stdin — install it (brew install jq / apt-get install jq).
.claude/settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Write it from the terminal (overwrites the file — paste your current one above first)
mkdir -p .claude && cat > .claude/settings.json <<'EOF'
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
EOFClaude Code's file watcher normally picks up hook edits without a restart. Run /hooks in a session to confirm the hook is registered and see which file it came from.
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/Users/you/my-project/src/index.ts",
"content": "export const hello = 'world'\n"
},
"tool_response": {
"filePath": "/Users/you/my-project/src/index.ts",
"type": "create"
},
"tool_use_id": "toolu_01ABC123",
"duration_ms": 12
}printf '%s\n' '{"session_id":"abc123","transcript_path":"/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd":"/Users/you/my-project","permission_mode":"default","hook_event_name":"PostToolUse","tool_name":"Write","tool_input":{"file_path":"/Users/you/my-project/src/index.ts","content":"export const hello = '\''world'\''\n"},"tool_response":{"filePath":"/Users/you/my-project/src/index.ts","type":"create"},"tool_use_id":"toolu_01ABC123","duration_ms":12}' | CLAUDE_PROJECT_DIR="$PWD" sh -c 'jq -r '\''.tool_input.file_path'\'' | xargs npx prettier --write'; echo "exit code: $?"Exit 0 = no objection (stdout goes to the debug log). Exit 2 on PostToolUse: Shows stderr to Claude — the tool already ran. Any other code is a non-blocking error. The command really runs — edit the sample paths before testing something that writes files.
What are hooks in Claude Code?
Hooks are user-defined handlers — usually shell commands — that Claude Code runs automatically at fixed points in its lifecycle. Where a CLAUDE.md instruction asks the model to remember something, a hook makes it happen every time: format after every edit, refuse writes to .env, run tests before Claude says it's done, ping your desktop when it needs you.
The configuration has three levels: a hook event (when), a matcher group (which occurrences), and one or more hook handlers (what runs). All of it lives under one hooks object in a settings file — add new events as siblings, don't replace the object:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }
]
}
]
}
}When an event fires and the matcher matches, Claude Code sends JSON describing the event to your handler — on stdin for command hooks — and reads back an exit code and optional JSON. Matching handlers run in parallel, and an identical handler defined in two settings files runs once.
Claude Code hooks events list (2026)
All 33 events in the current hooks reference. Per session: SessionStart and SessionEnd. Per turn: UserPromptSubmit, Stop, StopFailure. On every tool call: PreToolUse and PostToolUse. The rest fire around subagents, compaction, model switches, files, and MCP servers. Click an event name for its stdin fields and a sample payload.
| Event | Fires when | Matcher filters | Can block |
|---|---|---|---|
| SessionStart | a session starts, resumes, or restarts after /clear or compaction | How the session started | No |
| Setup | only with claude --init-only, or -p with --init or --maintenance — never on a normal start | Which flag triggered it | No |
| InstructionsLoaded | a CLAUDE.md or .claude/rules/*.md file loads into context — at start and when loaded lazily | Load reason | No |
| UserPromptSubmit | you submit a prompt, before Claude processes it | No matcher | Yes |
| UserPromptExpansion | a slash command or MCP prompt you typed expands into a prompt, before Claude sees it | Command name | Yes |
| MessageDisplay | assistant text streams to the screen, once per batch of completed lines | No matcher | No |
| PreToolUse | Claude has built a tool call, before it runs | Tool name | Yes |
| PermissionRequest | Claude Code is about to show you a permission prompt | Tool name | No |
| PermissionDenied | auto mode denies a tool call (not manual denials, deny rules, or hook blocks) | Tool name | No |
| PostToolUse | right after a tool call succeeds | Tool name | No |
| PostToolUseFailure | a tool that started running fails (not validation errors or permission denials) | Tool name | No |
| PostToolBatch | every tool call in a parallel batch has resolved, before the next model call | No matcher | Yes |
| Notification | Claude Code sends a notification — e.g. it needs permission or has been idle | Notification type | No |
| SubagentStart | a subagent is spawned or resumed | Agent type | No |
| SubagentStop | a subagent finishes responding | Agent type | Yes |
| TaskCreated | a task is being created with the TaskCreate tool | No matcher | Yes |
| TaskCompleted | a task is being marked completed | No matcher | Yes |
| Stop | the main agent finishes responding (not on your interrupt; API errors fire StopFailure) | No matcher | Yes |
| StopFailure | the turn ends because of an API error | Error type | No |
| TeammateIdle | an agent team teammate is about to go idle | No matcher | Yes |
| ConfigChange | a settings, managed policy, or skill file changes during a session | Configuration source | Yes |
| CwdChanged | the working directory changes, e.g. Claude runs cd | No matcher | No |
| DirectoryAdded | a working directory is added mid-session with /add-dir (or the SDK) | How it was added | No |
| FileChanged | a watched file changes on disk, whatever changed it | Literal filenames to watch, split on | | No |
| WorktreeCreate | a worktree is created (--worktree, isolation: worktree, background sessions) | No matcher | Yes |
| WorktreeRemove | a worktree is removed at session exit, subagent finish, or background-session delete | No matcher | Yes |
| PreCompact | right before context compaction | What triggered compaction | Yes |
| PostCompact | after compaction completes | What triggered compaction | No |
| PreModelSwitch | before a model switch you or a client requested (/model, the picker, /config) | Canonical name of the target model | Yes |
| PostModelSwitch | after the session's model changes, including automatic fallbacks and resume | Canonical name of the new model | No |
| SessionEnd | a session ends: exit, /clear, logout, or switching with /resume | Why it ended | No |
| Elicitation | an MCP server asks for user input during a tool call | MCP server name | Yes |
| ElicitationResult | you answered an MCP elicitation, before the answer goes back to the server | MCP server name | Yes |
Claude Code hooks reference: input, exit codes, output
Common input fields
Every event's JSON includes these, plus the event-specific fields listed further down:
| Field | Meaning |
|---|---|
session_id | Current session ID |
transcript_path | Path to the conversation JSONL — may lag the current turn; Stop hooks should read last_assistant_message instead |
cwd | Working directory when the hook runs (follows Claude into worktrees and cd) |
hook_event_name | The event that fired |
permission_mode | Default, plan, acceptEdits, auto, dontAsk, or bypassPermissions — not on every event |
prompt_id | UUID of the prompt being processed (absent before the first input) |
agent_id, agent_type | Present when the hook fires inside a subagent or with --agent |
effort | { level } on events inside a tool-use context, when the model supports effort |
Exit codes
| Exit code | What happens |
|---|---|
0 | Success. stdout goes to the debug log — except on UserPromptSubmit, UserPromptExpansion, SessionStart, and PostModelSwitch, where plain-text stdout is added to Claude's context. stdout that starts with { and ends with } is parsed as JSON output |
2 | Blocking error on events that can block: the action is stopped and stderr becomes the reason (a JSON blocking reason wins if you print one). Even a JSON "allow" can't override it. On events that can't block, see each event's row |
anything else | Non-blocking error: the action proceeds and the transcript shows a hook error notice with the first line of stderr. Exit 1 does not block — use 2 for policy hooks |
A mistyped script path exits 127 — a non-blocking error — so a broken policy hook silently lets everything through. Watch for the hook error notice on its first run.
JSON output fields any hook can return
| Field | Effect |
|---|---|
continue | false stops Claude entirely after the hook (takes precedence over decisions) |
stopReason | Message shown when continue is false |
systemMessage | Warning shown to you (some events discard it) |
terminalSequence | An allowlisted escape sequence Claude Code emits for you — desktop notifications (OSC 9 / 99 / 777), window titles, bell |
hookSpecificOutput | Event-specific control; must include hookEventName |
decision / reason | Top-level "block" for UserPromptSubmit, PostToolUse, Stop, SubagentStop, PreCompact, ConfigChange and similar |
additionalContext, systemMessage, and plain stdout are capped at 10,000 characters each; longer output is saved to a file and Claude gets the path plus a preview. Your stdout must contain only the JSON — a shell profile that echoes on startup breaks parsing.
Decision control by event
| Events | How to decide |
|---|---|
| PreToolUse | hookSpecificOutput.permissionDecision allow / deny / ask / defer + permissionDecisionReason; updatedInput rewrites the arguments. Across hooks, deny > defer > ask > allow |
| PermissionRequest | hookSpecificOutput.decision.behavior allow / deny, with updatedInput, updatedPermissions, message, interrupt |
| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | Top-level "decision": "block" + reason, and additionalContext where supported |
| PreModelSwitch | permissionDecision allow / deny / ask, or decision block |
| TeammateIdle, TaskCompleted, TaskCreated | Exit 2, or continue false (TaskCreated: decision block) |
| SessionStart, SubagentStart, PostModelSwitch | Context only — additionalContext |
| WorktreeCreate | Print the new path on stdout |
| Elicitation, ElicitationResult | Action accept / decline / cancel + content |
| Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged | None — side effects only |
A PreToolUse allow skips the prompt but never overrides a deny or ask rule from your settings — hooks can tighten permissions, not loosen them. A PreToolUse deny, on the other hand, blocks even in bypassPermissions mode.
Hook events: stdin fields and sample payloads
Session lifecycle
SessionStart— a session starts, resumes, or restarts after /clear or compaction
Matcher: How the session started — startup, resume, clear, compact, fork
Exit 2: Shows stderr to you only; the session continues.
JSON output: Plain stdout is added to Claude's context. JSON: additionalContext, sessionTitle, initialUserMessage (-p only), watchPaths, reloadSkills. Has access to $CLAUDE_ENV_FILE to persist env vars.
Handler types: command, mcp_tool
Note: Runs on every session, so keep it fast. Only command and mcp_tool hooks run here, and mcp_tool hooks are skipped at launch.
source— startup, resume, clear, compact, or forkmodel— Active model ID — sometimes omitted, check before readingagent_type— Present with claude --agent <name>session_title— Current title, if one is set
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "SessionStart",
"source": "startup",
"model": "claude-opus-5"
}Setup— only with claude --init-only, or -p with --init or --maintenance — never on a normal start
Matcher: Which flag triggered it — init, maintenance
Exit 2: Exit code and stderr are ignored.
JSON output: None — JSON output is discarded. Has access to $CLAUDE_ENV_FILE.
Handler types: command, mcp_tool
trigger— init or maintenance
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "Setup",
"trigger": "init"
}Notification— Claude Code sends a notification — e.g. it needs permission or has been idle
Matcher: Notification type — permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled
Exit 2: Exit code and stderr are ignored.
JSON output: None — side effects only. terminalSequence still fires (desktop notifications via your terminal).
Handler types: command, http, mcp_tool
Note: permission_prompt fires after the prompt waits ~6 seconds; idle_prompt ~60 seconds after Claude finishes. For an instant signal on permission prompts, use PermissionRequest.
message— Notification texttitle— Optional titlenotification_type— Which type fired (the matcher field)
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "Notification",
"message": "Claude needs your permission",
"title": "Permission needed",
"notification_type": "permission_prompt"
}SessionEnd— a session ends: exit, /clear, logout, or switching with /resume
Matcher: Why it ended — clear, resume, logout, prompt_input_exit, other
Exit 2: Shows stderr to you only.
JSON output: None — cleanup and logging; JSON output is discarded
Handler types: command, http, mcp_tool
Note: All SessionEnd hooks share a 1.5-second budget. A longer per-hook timeout raises it, up to 60 seconds.
reason— Why the session ended (the matcher field)
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "SessionEnd",
"reason": "other"
}Prompts and display
UserPromptSubmit— you submit a prompt, before Claude processes it
Matcher: Not supported; fires on every occurrence
Exit 2: Blocks the prompt and erases it; stderr is shown to you, not Claude.
JSON output: Plain stdout is added as context. JSON: decision "block" + reason; hookSpecificOutput.additionalContext, sessionTitle, suppressOriginalPrompt. It can't rewrite the prompt.
Handler types: command, http, mcp_tool, prompt, agent
Note: Default timeout is 30 seconds here (not 600), because it runs before every prompt.
prompt— The text you submitted, pasted content expanded
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
}UserPromptExpansion— a slash command or MCP prompt you typed expands into a prompt, before Claude sees it
Matcher: Command name — your skill or command names
Exit 2: Blocks the expansion; stderr is shown to you.
JSON output: JSON: decision "block" + reason; hookSpecificOutput.additionalContext
Handler types: command, http, mcp_tool, prompt, agent
Note: Typing /skill directly bypasses PreToolUse on the Skill tool — this event covers that path.
expansion_type— slash_command or mcp_promptcommand_name— The command (the matcher field)command_args— Text after the commandcommand_source— Where the command came from, e.g. pluginprompt— The original typed text
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "UserPromptExpansion",
"expansion_type": "slash_command",
"command_name": "deploy",
"command_args": "staging",
"command_source": "project",
"prompt": "/deploy staging"
}MessageDisplay— assistant text streams to the screen, once per batch of completed lines
Matcher: Not supported; fires on every occurrence
Exit 2: The original text is displayed.
JSON output: JSON: hookSpecificOutput.displayContent replaces what's shown on screen only — Claude and the transcript keep the original
Handler types: command, http, mcp_tool
Note: Default timeout is 10 seconds; each batch waits for your hook, so keep it fast.
turn_id— UUID of the turnmessage_id— UUID of the message, stable across batchesindex— 0-based batch index within the messagefinal— True on the last batchdelta— The newly completed lines
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "MessageDisplay",
"turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
"message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
"index": 0,
"final": false,
"delta": "Here is the plan:\n"
}Tool calls
PreToolUse— Claude has built a tool call, before it runs
Matcher: Tool name — Bash, Edit|Write, Read, mcp__.*, *
Exit 2: Blocks the tool call; stderr goes to Claude as the reason.
JSON output: JSON: hookSpecificOutput.permissionDecision allow | deny | ask | defer, permissionDecisionReason, updatedInput (replaces the whole input), additionalContext. Precedence across hooks: deny > defer > ask > allow.
Handler types: command, http, mcp_tool, prompt, agent
Note: "allow" skips the prompt but never overrides deny or ask rules. A deny from a hook blocks even in bypassPermissions mode. @file references in your prompt don't fire it.
tool_name— e.g. Bash, Edit, Write, mcp__github__create_issuetool_input— The tool's arguments — file_path is always absolutetool_use_id— ID of this callmcp_server— For MCP tools: { name, source }
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite",
"timeout": 120000,
"run_in_background": false
},
"tool_use_id": "toolu_01ABC123"
}PermissionRequest— Claude Code is about to show you a permission prompt
Matcher: Tool name — Bash, ExitPlanMode, Edit|Write, mcp__.*
Exit 2: Not honored — the permission flow proceeds unchanged.
JSON output: JSON: hookSpecificOutput.decision.behavior allow | deny, plus updatedInput / updatedPermissions (allow) or message / interrupt (deny)
Handler types: command, http, mcp_tool, prompt
Note: Keep the matcher narrow: an empty matcher that returns allow approves every prompt.
tool_name— The tool asking for permissiontool_input— Its arguments (no tool_use_id)permission_suggestions— The rule / mode updates Claude Code suggests
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [
{
"toolName": "Bash",
"ruleContent": "rm -rf node_modules"
}
],
"behavior": "allow",
"destination": "localSettings"
}
]
}PermissionDenied— auto mode denies a tool call (not manual denials, deny rules, or hook blocks)
Matcher: Tool name — Bash, *
Exit 2: Ignored — the denial already happened.
JSON output: JSON: hookSpecificOutput.retry true tells the model it may retry
Handler types: command, http, mcp_tool, prompt, agent
tool_name— The denied tooltool_input— Its argumentstool_use_id— ID of the callreason— Why auto mode denied it
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123",
"reason": "[Irreversible Local Destruction]"
}PostToolUse— right after a tool call succeeds
Matcher: Tool name — Edit|Write, Bash, mcp__.*, *
Exit 2: Shows stderr to Claude — the tool already ran.
JSON output: JSON: decision "block" + reason (feedback next to the result), hookSpecificOutput.additionalContext, updatedToolOutput (replaces what Claude sees)
Handler types: command, http, mcp_tool, prompt, agent
Note: Doesn't fire when Bash or another process rewrites the file — use FileChanged for that.
tool_name— The tool that rantool_input— Its argumentstool_response— The tool's result — shape depends on the tooltool_use_id— ID of the callduration_ms— Execution time, when available
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/Users/you/my-project/src/index.ts",
"content": "export const hello = 'world'\n"
},
"tool_response": {
"filePath": "/Users/you/my-project/src/index.ts",
"type": "create"
},
"tool_use_id": "toolu_01ABC123",
"duration_ms": 12
}PostToolUseFailure— a tool that started running fails (not validation errors or permission denials)
Matcher: Tool name — Bash, *
Exit 2: Shows stderr to Claude — the tool already failed.
JSON output: JSON: hookSpecificOutput.additionalContext
Handler types: command, http, mcp_tool, prompt, agent
tool_name— The tool that failedtool_input— Its argumentserror— What went wrong — for Bash, starts with Exit code Nis_interrupt— True when it was abortedduration_ms— Execution time
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123",
"error": "Exit code 1\nError: Cannot find module 'express'",
"is_interrupt": false,
"duration_ms": 4187
}PostToolBatch— every tool call in a parallel batch has resolved, before the next model call
Matcher: Not supported; fires on every occurrence
Exit 2: Stops the agentic loop before the next model call.
JSON output: JSON: hookSpecificOutput.additionalContext (once per batch); decision "block" or continue false stops the loop
Handler types: command, http, mcp_tool, prompt, agent
tool_calls— Array of { tool_name, tool_input, tool_use_id, tool_response } — serialized as the model sees it
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PostToolBatch",
"tool_calls": [
{
"tool_name": "Read",
"tool_input": {
"file_path": "/Users/you/my-project/ledger/accounts.py"
},
"tool_use_id": "toolu_01",
"tool_response": " 1\tfrom __future__ import annotations\n"
}
]
}Subagents, tasks, teams
SubagentStart— a subagent is spawned or resumed
Matcher: Agent type — general-purpose, Explore, Plan, your-agent-name
Exit 2: Shows stderr in the subagent's transcript only.
JSON output: JSON: hookSpecificOutput.additionalContext is injected into the subagent
Handler types: command, http, mcp_tool
agent_id— Unique ID of the subagentagent_type— Agent name (the matcher field)
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
}SubagentStop— a subagent finishes responding
Matcher: Agent type — general-purpose, Explore, Plan, your-agent-name
Exit 2: Keeps the subagent running; stderr becomes its next instruction.
JSON output: Same as Stop: decision "block" + reason, or hookSpecificOutput.additionalContext
Handler types: command, http, mcp_tool, prompt, agent
stop_hook_active— True if already continuing because of a stop hookagent_id— Subagent IDagent_type— Agent name (the matcher field)agent_transcript_path— The subagent's own transcriptlast_assistant_message— The subagent's final textbackground_tasks / session_crons— In-flight work of the parent session
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "/Users/you/.claude/projects/my-project/abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues...",
"background_tasks": [],
"session_crons": []
}TaskCreated— a task is being created with the TaskCreate tool
Matcher: Not supported; fires on every occurrence
Exit 2: Rolls back the task; stderr goes to Claude as the tool error.
JSON output: JSON: decision "block" + reason cancels the task (continue false is ignored)
Handler types: command, http, mcp_tool, prompt, agent
task_id— Task identifiertask_subject— Task titletask_description— Details, may be absentteammate_name— Creating teammate, may be absent
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "TaskCreated",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints"
}TaskCompleted— a task is being marked completed
Matcher: Not supported; fires on every occurrence
Exit 2: The task stays open; stderr goes back to the model.
JSON output: Exit 2, or continue false + stopReason (stops a teammate; ignored when TaskUpdate triggered it)
Handler types: command, http, mcp_tool, prompt, agent
task_id— Task identifiertask_subject— Task titletask_description— Details, may be absentteammate_name— Completing teammate, may be absent
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication"
}TeammateIdle— an agent team teammate is about to go idle
Matcher: Not supported; fires on every occurrence
Exit 2: The teammate keeps working with stderr as feedback.
JSON output: Exit 2, or continue false + stopReason to stop the teammate
Handler types: command, http, mcp_tool, prompt, agent
teammate_name— The teammate going idle
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher"
}End of turn
Stop— the main agent finishes responding (not on your interrupt; API errors fire StopFailure)
Matcher: Not supported; fires on every occurrence
Exit 2: Claude keeps working; stderr becomes the reason to continue.
JSON output: JSON: decision "block" + reason (required), or hookSpecificOutput.additionalContext for non-error feedback. Capped at 8 consecutive blocks.
Handler types: command, http, mcp_tool, prompt, agent
stop_hook_active— True when already continuing because of a stop hook — check it to avoid loopslast_assistant_message— Claude's final text this turnbackground_tasks— In-flight shells, subagents, monitors…session_crons— Scheduled wakeups from /loop and cron tools
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "Stop",
"stop_hook_active": false,
"last_assistant_message": "I've completed the refactoring. Here's a summary...",
"background_tasks": [],
"session_crons": []
}StopFailure— the turn ends because of an API error
Matcher: Error type — rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown
Exit 2: Output and exit code are ignored (except terminalSequence).
JSON output: None — logging and alerts only
Handler types: command, http, mcp_tool
Note: Its matcher only treats letters, digits, _ and | as exact; anything else is a regex.
error— Error type (the matcher field)error_details— More detail, when availablelast_assistant_message— The rendered API error text
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "StopFailure",
"error": "rate_limit",
"error_details": "429 Too Many Requests",
"last_assistant_message": "API Error: Rate limit reached"
}Context and model
InstructionsLoaded— a CLAUDE.md or .claude/rules/*.md file loads into context — at start and when loaded lazily
Matcher: Load reason — session_start, nested_traversal, path_glob_match, include, compact
Exit 2: Exit code is ignored.
JSON output: None — observability only; JSON output is discarded
Handler types: command, http, mcp_tool
file_path— Absolute path of the loaded filememory_type— User, Project, Local, or Managedload_reason— Why it loaded (the matcher field)globs— paths: patterns, for path_glob_match loadstrigger_file_path— File whose access triggered a lazy loadparent_file_path— Including file, for include loads
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/you/my-project/CLAUDE.md",
"memory_type": "Project",
"load_reason": "session_start"
}PreCompact— right before context compaction
Matcher: What triggered compaction — manual, auto
Exit 2: Blocks compaction (for manual /compact, stderr is shown to you).
JSON output: JSON: decision "block"
Handler types: command, http, mcp_tool
trigger— manual or autocustom_instructions— What you passed to /compact, or null
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": null
}PostCompact— after compaction completes
Matcher: What triggered compaction — manual, auto
Exit 2: Shows stderr to you only.
JSON output: None
Handler types: command, http, mcp_tool
trigger— manual or autocompact_summary— The generated summary
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "PostCompact",
"trigger": "manual",
"compact_summary": "Summary of the compacted conversation..."
}PreModelSwitch— before a model switch you or a client requested (/model, the picker, /config)
Matcher: Canonical name of the target model — claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.*
Exit 2: Blocks the switch and shows stderr to you.
JSON output: JSON: hookSpecificOutput.permissionDecision allow | deny | ask, or decision "block". A timeout also blocks.
Handler types: command, http, mcp_tool
Note: Requires v2.1.251+. Default timeout 30 seconds.
from_model / to_model— Model IDsrequested_model— Alias or ID requested, or nullsource— command, picker, or sdkcontext_tokens— Tokens the next request re-sendsprompt_cache_warm / cache_ttl— Cache stateestimated_cache_write_usd / pricing— Cost estimate of switching
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "PreModelSwitch",
"from_model": "claude-sonnet-5",
"to_model": "claude-opus-5",
"requested_model": "opus",
"source": "command",
"context_tokens": 182340,
"prompt_cache_warm": true,
"cache_ttl": "5m",
"estimated_cache_write_usd": 1.1396,
"pricing": "catalog"
}PostModelSwitch— after the session's model changes, including automatic fallbacks and resume
Matcher: Canonical name of the new model — .*opus.*, claude-sonnet-5
Exit 2: Shows stderr to you only.
JSON output: Plain stdout or hookSpecificOutput.additionalContext reaches Claude with the next request
Handler types: command, http, mcp_tool
Note: Requires v2.1.251+. Default timeout 30 seconds.
same as PreModelSwitch— Plus source auto or resume
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "PostModelSwitch",
"from_model": "claude-sonnet-5",
"to_model": "claude-opus-5",
"requested_model": "opus",
"source": "command",
"context_tokens": 182340,
"prompt_cache_warm": true,
"cache_ttl": "5m",
"estimated_cache_write_usd": 1.1396,
"pricing": "catalog"
}Files, directories, config
ConfigChange— a settings, managed policy, or skill file changes during a session
Matcher: Configuration source — user_settings, project_settings, local_settings, policy_settings, skills
Exit 2: Blocks the change from applying (never for policy_settings); no message is shown.
JSON output: JSON: decision "block"
Handler types: command, http, mcp_tool
source— Which kind of config changed (the matcher field)file_path— The file that changed
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "ConfigChange",
"source": "project_settings",
"file_path": "/Users/you/my-project/.claude/settings.json"
}CwdChanged— the working directory changes, e.g. Claude runs cd
Matcher: Not supported; fires on every occurrence
Exit 2: Shows stderr to you only.
JSON output: JSON: watchPaths (sets FileChanged's dynamic watch list), systemMessage. Has access to $CLAUDE_ENV_FILE.
Handler types: command, http, mcp_tool
old_cwd— Previous directorynew_cwd— New directory
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project/src",
"hook_event_name": "CwdChanged",
"old_cwd": "/Users/you/my-project",
"new_cwd": "/Users/you/my-project/src"
}DirectoryAdded— a working directory is added mid-session with /add-dir (or the SDK)
Matcher: How it was added — slash_command, register_repo_root
Exit 2: Stderr goes to the debug log; the directory is already added.
JSON output: None — runs in the background
Handler types: command, http, mcp_tool
directory— Absolute path addedsource— slash_command or register_repo_root
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "DirectoryAdded",
"directory": "/Users/you/my-other-repo",
"source": "slash_command"
}FileChanged— a watched file changes on disk, whatever changed it
Matcher: Literal filenames to watch, split on | — .envrc|.env, package.json, data.csv
Exit 2: Shows stderr to you only.
JSON output: JSON: watchPaths, systemMessage. Has access to $CLAUDE_ENV_FILE.
Handler types: command, http, mcp_tool
Note: The matcher is a list of literal filenames in the working directory, not a regex.
file_path— Absolute path of the changed fileevent— change, add, or unlink
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "FileChanged",
"file_path": "/Users/you/my-project/.envrc",
"event": "change"
}WorktreeCreate— a worktree is created (--worktree, isolation: worktree, background sessions)
Matcher: Not supported; fires on every occurrence
Exit 2: Any non-zero exit fails worktree creation.
JSON output: Print the new worktree's path as the last stdout line (HTTP hooks return worktreePath)
Handler types: command, http, mcp_tool
Note: Configuring it replaces the default Git worktree behavior entirely — meant for SVN, Perforce, Mercurial.
name— Slug for the new worktree
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}WorktreeRemove— a worktree is removed at session exit, subagent finish, or background-session delete
Matcher: Not supported; fires on every occurrence
Exit 2: Any non-zero exit fails removal if the directory still exists.
JSON output: Exit code only; JSON output is discarded
Handler types: command, http, mcp_tool
worktree_path— Absolute path being removed
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/you/my-project/.claude/worktrees/feature-auth"
}MCP elicitation
Elicitation— an MCP server asks for user input during a tool call
Matcher: MCP server name — your mcp server names
Exit 2: Denies the elicitation (stderr isn't shown).
JSON output: JSON: hookSpecificOutput.action accept | decline | cancel, content (form values)
Handler types: command, http, mcp_tool
mcp_server_name— The server (the matcher field)message— What it's askingmode— form or urlurl / requested_schema / elicitation_id— Mode-specific details
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please provide your credentials",
"mode": "form",
"requested_schema": {
"type": "object",
"properties": {
"username": {
"type": "string",
"title": "Username"
}
}
}
}ElicitationResult— you answered an MCP elicitation, before the answer goes back to the server
Matcher: MCP server name — your mcp server names
Exit 2: Blocks the response — the action becomes decline.
JSON output: JSON: hookSpecificOutput.action and content override your answer
Handler types: command, http, mcp_tool
mcp_server_name— The serveraction— accept, decline, or cancelcontent / mode / elicitation_id— The answer and details
{
"session_id": "abc123",
"transcript_path": "/Users/you/.claude/projects/my-project/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/you/my-project",
"hook_event_name": "ElicitationResult",
"mcp_server_name": "my-mcp-server",
"action": "accept",
"content": {
"username": "alice"
},
"mode": "form",
"elicitation_id": "elicit-123"
}Matchers and the if field
"*","", or no matcher matches everything.- Letters, digits,
_,-, spaces,,and|only: an exact name or list —Edit|WriteandEdit, Writeare the same. Matching is case-sensitive. - Any other character makes it an unanchored JavaScript regex:
^Notebook,mcp__memory__.*.Edit.*also matches NotebookEdit — anchor with^…$. - MCP tools are named
mcp__<server>__<tool>. A baremcp__memoryis an exact string that matches no tool; usemcp__memory__.*. - Events without matcher support (UserPromptSubmit, Stop, PostToolBatch, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay) silently ignore one.
- FileChanged's matcher is a
|-separated list of literal filenames to watch, not a regex. ifnarrows a single handler with one permission rule —Bash(git *),Edit(*.ts)— and only on PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, and PermissionDenied. On any other event a handler withifnever runs. It's best-effort, so enforce hard rules with permissions.
Hook types: command, http, mcp_tool, prompt, agent
| Type | What it does | Default timeout |
|---|---|---|
command | Runs a shell command (sh -c on macOS/Linux); JSON on stdin, results via exit code + stdout. Add args for exec form without a shell; async: true runs it in the background | 600 s |
http | Posts the JSON to url; the response body uses the same JSON output. Status codes can't block — return a 2xx with a decision. Headers can use $VARS listed in allowedEnvVars | 600 s |
mcp_tool | Calls tool on an already-connected MCP server; input supports ${tool_input.file_path}-style substitution | 600 s |
prompt | Sends your prompt ($ARGUMENTS = the input JSON) to a fast model, which answers {"ok": true} or {"ok": false, "reason": …} | 30 s |
agent | Like prompt, but a subagent that can read files and run tools for up to 50 turns. Experimental | 60 s |
UserPromptSubmit, PreModelSwitch, and PostModelSwitch lower the command/http/mcp_tool default to 30 seconds, MessageDisplay to 10, and all SessionEnd hooks share a 1.5-second budget. SessionStart and Setup only run command and mcp_tool hooks; prompt and agent hooks work on the tool, prompt, stop, and task events.
Where hooks live
| Location | Scope |
|---|---|
~/.claude/settings.json | All your projects, this machine |
.claude/settings.json | One project — commit it to share with the team |
.claude/settings.local.json | One project, just you |
| Managed policy settings | Organization-wide, set by admins |
| Plugin hooks/hooks.json | While the plugin is enabled |
| Skill or subagent frontmatter | While that skill or subagent is active |
Hooks from all of these merge rather than override. Reference project scripts as "$CLAUDE_PROJECT_DIR"/.claude/hooks/my-hook.sh so they resolve no matter where Claude has cd'd, and chmod +x them. /hooks shows a read-only list of everything registered and which file it came from; "disableAllHooks": true turns them all off.
Claude Code hooks examples
The recipes in the builder cover the common cases. For anything longer than a line, move the logic into a script — this is the protected-files example from the official guide, saved as .claude/hooks/protect-files.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}" # normalize windows separators
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0Register it with a PreToolUse hook on Edit|Write whose command is "$CLAUDE_PROJECT_DIR"/.claude/hooks/protect-files.sh. And a Stop hook that blocks must check stop_hook_active first, or Claude can keep being sent back — Claude Code gives up after eight consecutive blocks:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continued once: let claude stop
fi
# ... your check; exit 2 with a reason on stderr to keep claude workingSecurity and debugging
- Hooks run with your full user permissions. Review every command, quote shell variables (
"$VAR"), and skip sensitive paths. - Workspace trust: in an interactive session, hooks wait until you accept the trust dialog for the folder.
claude -pand SDK runs treat the folder as trusted — review a repo's.claude/before scripting against it, or pass--settings '{"disableAllHooks": true}'. - Debug: start with
claude --debug-file /tmp/claude.logandtail -fit to see which hooks matched, their exit codes, stdout, and stderr.Ctrl+Oopens the transcript view. - JSON ignored? A field at the wrong level (permissionDecision belongs inside hookSpecificOutput) is dropped silently, and anything printed before the JSON makes Claude Code treat it as plain text.
Keep going
Hooks enforce; commands and skills instruct. Build a reusable prompt like /commit or /review-pr with the Claude Code slash command generator, and write the project context Claude reads every session with the CLAUDE.md generator. To compare two versions of a settings file, use the JSON diff checker.
Frequently asked questions
What are hooks in Claude Code?
Hooks are commands you configure in settings.json that Claude Code runs automatically at points in its lifecycle — before a tool call, after a file edit, when Claude stops, when a session starts. They're deterministic: a formatter hook runs after every edit whether or not the model remembers to. Besides shell commands, a hook can post to a URL, call an MCP tool, or ask a Claude model (prompt and agent hooks).
What hook events does Claude Code support in 2026?
The hooks reference lists SessionStart, Setup, InstructionsLoaded, UserPromptSubmit, UserPromptExpansion, MessageDisplay, PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatch, Notification, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Stop, StopFailure, TeammateIdle, ConfigChange, CwdChanged, DirectoryAdded, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, PreModelSwitch, PostModelSwitch, SessionEnd, Elicitation, and ElicitationResult — 33 events. The table on this page lists when each fires and whether it can block.
Where do I put Claude Code hooks?
In a settings file, under a top-level "hooks" key: ~/.claude/settings.json applies to all your projects, .claude/settings.json is shared with the repo, and .claude/settings.local.json is just for you in one project. Plugins, skill frontmatter, subagent frontmatter, and managed policy settings can define hooks too. Hooks from every level are merged, not overridden.
How does a hook block an action?
Exit with code 2 and write the reason to stderr. On events that can block — PreToolUse, UserPromptSubmit, Stop, SubagentStop, PreCompact, ConfigChange and others — that stops the action, and for PreToolUse and Stop Claude sees your stderr as the reason. Exit 1 doesn't block: it's a non-blocking error and the action proceeds. For finer control, exit 0 and print JSON, e.g. hookSpecificOutput.permissionDecision "deny" on PreToolUse.
What JSON does a hook receive?
Every hook gets session_id, transcript_path, cwd, and hook_event_name on stdin (HTTP hooks get it as the POST body), plus permission_mode and event-specific fields: tool_name, tool_input and tool_use_id for tool events, tool_response for PostToolUse, prompt for UserPromptSubmit, source for SessionStart, stop_hook_active and last_assistant_message for Stop. The test panel above shows the exact sample for any event.
What's the difference between matcher and if?
Matcher filters a whole group of handlers by one field — the tool name for tool events, the source for SessionStart, the notification type for Notification. If is set on a single handler, only works on tool events, and uses permission-rule syntax to look at arguments too, like Bash(git *) or Edit(*.ts), so the process isn't even spawned for other calls.
How do I auto-format files after Claude edits them?
Add a PostToolUse hook with the matcher Edit|Write and a command that reads the edited path from stdin, e.g. jq -r '.tool_input.file_path' | xargs npx prettier --write. The recipes above include Prettier, Ruff, and gofmt versions. PostToolUse doesn't fire when a Bash command rewrites a file — use a FileChanged hook for that.
How do I get a notification when Claude Code needs input?
Add a Notification hook in ~/.claude/settings.json. On macOS: osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'; on Linux: notify-send. permission_prompt fires after a prompt has waited about six seconds, and idle_prompt about 60 seconds after Claude finishes; use PermissionRequest for an instant signal.
Why isn't my hook running?
Run /hooks to check it's registered under the right event. Matchers are case-sensitive (Bash, not bash). Make sure the settings file is valid JSON — no comments or trailing commas — and that script files are executable. The if field on a non-tool event stops the hook from ever running. In an interactive session, hooks wait until you accept the workspace trust dialog. claude --debug-file /tmp/claude.log records every match, exit code, stdout and stderr.
How do I disable hooks?
Set "disableAllHooks": true in a settings file, or pass --settings '{"disableAllHooks": true}' for one run. There's no switch for a single hook — remove its entry instead. Managed hooks can only be disabled from managed settings.
Related free tools
See all free tools →Built something? Put it online in seconds
host0 is the cloud for small software: bring any coding agent, build the tool only you need — like this one — and say "deploy to host0". Live at a shareable URL, no servers to run.