Hook Input and Output
Each hook gets a JSON description of what happened. It answers with an exit code, or with exit 0 and a JSON object.
The answer tells Claude Code to continue, to block, or to add context.
For example, exit code 2 with a message on stderr blocks a PreToolUse call and tells Claude why.
You control the answer your hook gives, not the input. Each event decides which fields it reads.
Example
A command hook reads the input. Then it answers with JSON to ask you before it runs terraform apply.
#!/bin/bash
input=$(cat)
cmd=$(jq -r '.tool_input.command // ""' <<<"$input")
if [[ "$cmd" == terraform\ apply* ]]; then
cat <<'JSON'
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "terraform apply changes real infrastructure"
}
}
JSON
fi
exit 0
The script exits 0 and prints one JSON object. Claude Code reads the object. Then it shows a permission prompt.
Common Input Fields
A command hook reads this JSON on stdin. An HTTP hook gets it as the POST body.
| Field | Status | What it holds | Notes |
|---|---|---|---|
session_ | Documented: An official docs page names this item. | The session identifier. | A string. |
transcript_ | Documented: An official docs page names this item. | The path to the conversation JSONL file. | Claude Code writes it async, so it can lag. On Stop, use last_. See Transcript Format. |
cwd | Documented: An official docs page names this item. | The working directory when the hook runs. | Follows cd and worktrees. |
prompt_ | Documented: An official docs page names this item. | The UUID of the prompt in progress. | Equals prompt. in OpenTelemetry events. Absent before the first input. |
scratchpad_ | Differs: The official docs and the program disagree. The row says how. In the docs and the program string list, but missing from BaseHookInput in the type file. | The session scratchpad folder. | Needs 2.1.257. Sent on most events. |
permission_ | Documented: An official docs page names this item. | default, plan, accept, auto, dont, or bypass. | Claude Code sends manual mode as default. Not on every event. |
effort | Documented: An official docs page names this item. | An object {level} with low, medium, high, xhigh, or max. | Only in tool-use context, and only when the model supports effort. Claude Code sends it on tool events, Stop, and Subagent. |
hook_ | Documented: An official docs page names this item. | The name of the event. | In the type file it is a literal on each event type, not on Base. |
agent_ | Documented: An official docs page names this item. | The subagent or in-process teammate that the hook fires in. | Absent on the main thread. On task and teammate events it names the actor (2.1.290). |
agent_ | Documented: An official docs page names this item. | The agent name. | Set in a subagent, or on the main thread with --. |
mcp_ | Documented: An official docs page names this item. | An object {name, source} for an MCP tool. | Needs 2.1.274. source is an open set: sdk, plugin, user, project, local, dynamic, managed, enterprise, claudeai, agent. |
Universal Output Fields
A hook prints one JSON object to stdout. Then it exits 0.
Every event accepts these keys. Some events drop them. Events says which.
| Field | Status | What it does | Notes |
|---|---|---|---|
continue | Documented: An official docs page names this item. | false stops Claude after the hook. | Beats event decisions. Task ignores it. Task also ignores it when it comes from Task. |
stop | Documented: An official docs page names this item. | The message to show when continue is false. | Stays in the conversation. |
system | Documented: An official docs page names this item. | A warning for you. | Some events drop it or send it somewhere else. See the table below. |
terminal | Documented: An official docs page names this item. | An escape sequence that Claude Code writes for you. | Allowed: OSC 0, 1, 2, 9, 99, 777, and BEL. Interactive mode only. Works on events that drop other fields. Not for Worktree command hooks. |
decision | Differs: The official docs and the program disagree. The row says how. Docs: only block. Program enum: approve or block for any event. On PreToolUse both are deprecated. | A top-level block. | See Decision Patterns below. |
reason | Documented: An official docs page names this item. | The text for the block. | You or Claude sees it, by event. |
hook | Documented: An official docs page names this item. | An event-specific object. | Needs hook set to the event name. |
{"async": | Undocumented: Only the program shows this item. It can change without notice. | A first reply that moves the hook to the background. | See Configuration. Command hooks only. |
| 10,000 character cap | Documented: An official docs page names this item. | Applies to additional, system, initial, and plain stdout. | Each string alone. If a string is over the cap, Claude Code saves it to a file. The model gets the path and the first 2,000 characters. |
What continue and systemMessage Do per Event
A probe ran a command hook on each event in -p mode. "Stops" means the model made no more requests. Some messages show only in the interactive screen, so "Not shown" means not shown in -p stream-json. Source: Probes.
| Event | continue: false | systemMessage |
|---|---|---|
Session | Not honored. The session went on. | Not shown. |
User | Honored. No model request. The result is Operation stopped by hook:. | Shown as User, but not together with a stop. |
User | Honored, with the same result text. | Shown as User. |
Pre | Honored. The loop stops after the hook. The stop is not shown. | Shown as Pre. |
Post | Honored. The loop stops. | Shown only without a stop. |
Post | Not honored. | Shown. |
Post | Honored. | Shown. |
Stop | The text Stop hook stopped continuation: enters the conversation. | Shown as Stop says:. |
Subagent, Subagent, Session | The hook ran. No visible effect. | Not shown. |
Instructions, Setup | No effect. | Not shown. |
PermissionRequest, PermissionDenied, Notification, and PreCompact do not fire in -p. They fired in a probe of the interactive screen (2.1.305, local stub, fake key). The rows below are from that run. Source: Interactive Probes.
| Event | continue: false | systemMessage |
|---|---|---|
Permission | Not honored. A hook that also allowed the call still ran the tool. Use hook. | Not shown on screen or in the transcript. |
Permission | Not honored. The model made its next request. | Not shown. |
Notification | Not honored. The permission dialog stayed open and worked. | Not shown. |
Pre | Not honored. Compaction ran and Post fired. Block it with decision: or exit 2. | Not parsed. Claude Code prints the raw stdout JSON on the hook's completed successfully line. |
What Fires and What Is Honored in the Interactive Screen
| Event | When it fires | What a reply does |
|---|---|---|
Permission | Before the permission dialog opens, in the same second as Pre. It also fires in auto mode, but only when auto mode falls back to a dialog (checked after 3 blocked actions in a row). It did not fire for a plain auto mode denial. | decision.: no dialog, the tool ran, the line says Allowed by Permission. "deny" with message: no dialog, not run, the model gets the message text, the line says Denied by Permission. deny with interrupt:: not run, the turn stops with Interrupted. Claude Code ignores exit 2 with no output and a top-level decision:. The normal dialog opens. {} or no output: the normal dialog. |
Permission | Only for an auto mode denial. A manual "No" in the dialog does not fire it. The input has tool_ and reason (mcp_ is absent for a built-in tool). | hook with a classifier verdict: the denial stands, and the model gets the extra text The Permission after the tool result. With no verdict (the classifier failed), it changes nothing. |
Notification | The permission dialog fires it after about 6 seconds. Answer sooner and it never fires. The input has notification_ and message (no title in these runs). | Cannot change the dialog. For idle_ nothing visible changed. |
Pre | On /, with trigger: and custom_. With too few messages (Not enough messages to compact.) it does not fire. Post follows with compact_. | decision: with reason: Compaction blocked by Pre. Exit 2: Compaction blocked by Pre. |
An auto mode run with a custom base URL shows a classifier billing notice first. It fires a Notification with notification_type: "agent_needs_input".
Decision Patterns
| Pattern | Status | What it does | Events |
|---|---|---|---|
Top-level decision: | Documented: An official docs page names this item. | Blocks. reason is the message. | User, User, Post, Post, Post, Stop, Subagent, Config, Pre, Task |
hook | Documented: An official docs page names this item. | allow, deny, ask, or defer. defer works only in -. | Pre. Pre has no defer. |
hook | Documented: An official docs page names this item. | allow or deny. Needs a decision object. | Permission |
continue: or exit 2 | Documented: An official docs page names this item. | Stops the teammate. | Teammate, Task |
| Path return | Documented: An official docs page names this item. | A command prints the path. An HTTP hook returns worktree. A failure fails the creation. | Worktree |
| Context only | Documented: An official docs page names this item. | additional. It cannot block. | Session, Subagent, Post, Setup, Notification |
| None | Documented: An official docs page names this item. | Side effects only. | Session, Post, Instructions, Stop, Cwd, Directory, File. The last two still return watch. |
Exit Codes
| Exit code | Status | What it does | Notes |
|---|---|---|---|
| 0 | Documented: An official docs page names this item. | Success. | Claude Code reads JSON from stdout. Plain stdout becomes context only on User, User, Session, and Post. Claude never sees stderr from an exit 0 hook. |
| 2 | Documented: An official docs page names this item. | A blocking error on events that can block. | stderr is the reason. Claude Code reads JSON fields. They cannot override the block. Elicitation and Elicitation ignore them. |
| 1 | Documented: An official docs page names this item. | Not a block. | Exit 1 is a non-blocking error. Use exit 2 for policy. |
| Any other code | Documented: An official docs page names this item. | A non-blocking error. | The action continues. The notice has the first line of stderr. JSON on stdout can still decide. |
| Invalid JSON | Documented: An official docs page names this item. | A non-blocking error. | The rules for one object, text, and several JSON lines are in the Exit code 0 section of the Hooks Reference. |
Pick one style per hook. Use exit codes alone, or exit 0 with JSON. If you mix them, exit 2 keeps its blocking effect. Claude Code still reads the JSON fields.
What Exit 2 Does per Event
| Effect | Status | Events |
|---|---|---|
| Blocks the action | Documented: An official docs page names this item. | Pre, User, User, Stop, Subagent, Teammate, Task, Task, Config (not policy_), Post, Pre, Pre, Elicitation, Elicitation |
| Fails the step | Documented: An official docs page names this item. | Worktree always. Worktree if the folder still exists. |
Shows stderr to Claude, no block | Documented: An official docs page names this item. | Post, Post |
Shows stderr to you, no block | Documented: An official docs page names this item. | Subagent, Session, Session, Cwd, File, Post, Post |
| Ignored | Documented: An official docs page names this item. | Permission, Permission, Notification, Setup, Stop, Instructions, Message, and Directory (its stderr goes to the debug log) |
HTTP Responses
| Response | Status | What it does |
|---|---|---|
| 2xx with an empty body | Documented: An official docs page names this item. | Success. The same as exit 0 with no output. |
| 2xx with a JSON object | Documented: An official docs page names this item. | Claude Code parses it like command JSON. A body that fails validation is a non-blocking error. |
| 2xx with other text, non-2xx, or a connection error | Documented: An official docs page names this item. | A non-blocking error. Claude Code does not add the text to context. on blocks. |
How It Works
Exact Fields
The hook input is in the *HookInput types in the Mod API type file https://clawdipedia.cc/schemas/claude-code.d.ts. The hook output is in https://clawdipedia.cc/schemas/claude-code-hook-output.json. The program's own validator makes it, with one definition for each hookSpecificOutput.
It lists every field of this release. Build on the fields it names. Look each one up there before you rely on it.
Related Pages
- Hooks: the overview.
- Events: the fields of each event.
- Handlers: what each handler type can return.
- Agent Loop: a
PreToolUseexit 2 example in context.
Source: Hooks Reference and the program (BaseHookInput, McpServerProvenance, ClassicResultFields, and the hook output schema).
We checked the exit code 2 table against the Hooks Reference. The continue and systemMessage results come from probes in -p mode with a local stub (see Probes).