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 go on, 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 answers with JSON to ask you before running 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 it and 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 | The session identifier. | A string. |
transcript_ | Documented | The path to the conversation JSONL file. | Written async, so it can lag. On Stop, use last_. See Transcript Format. |
cwd | Documented | The working directory when the hook runs. | Follows cd and worktrees. |
prompt_ | Documented | The UUID of the prompt in progress. | Equals prompt. in OpenTelemetry events. Absent before the first input. |
scratchpad_ | Differs | The session scratchpad folder. | Needs 2.1.257. Sent on most events. |
permission_ | Documented | default, plan, accept, auto, dont, or bypass. | Manual mode is sent as default. Not on every event. |
effort | Documented | An object {level} with low, medium, high, xhigh, or max. | Only in tool-use context, and only when the model supports effort. Sent on tool events, Stop, and Subagent. |
hook_ | Documented | The name of the event. | In the type file it is a literal on each event type, not on Base. |
agent_ | Documented | 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 | The agent name. | Set in a subagent, or on the main thread with --. |
mcp_ | Documented | 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 and exits 0.
Every event accepts these keys. Some events drop them, and Events says which.
| Field | Status | What it does | Notes |
|---|---|---|---|
continue | Documented | false stops Claude after the hook. | Beats event decisions. Ignored by Task, and by Task when it comes from Task. |
stop | Documented | The message to show when continue is false. | Stays in the conversation. |
system | Documented | A warning for you. | Some events drop it or send it somewhere else. |
terminal | Documented | 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 | A top-level block. | See Decision Patterns below. |
reason | Documented | The text for the block. | You or Claude sees it, by event. |
hook | Documented | An event-specific object. | Needs hook set to the event name. |
{"async": | Undocumented | A first reply that moves the hook to the background. | See Configuration. Command hooks only. |
| 10,000 character cap | Documented | Applies to additional, system, initial, and plain stdout. | Each string alone. Over the cap, Claude Code saves it to a file, and the model gets the path and the first 2,000 characters. |
Decision Patterns
| Pattern | Status | What it does | Events |
|---|---|---|---|
Top-level decision: | Documented | Blocks. reason is the message. | User, User, Post, Post, Post, Stop, Subagent, Config, Pre, Task |
hook | Documented | allow, deny, ask, or defer. defer works only in -. | Pre. Pre has no defer. |
hook | Documented | allow or deny. Needs a decision object. | Permission |
continue: or exit 2 | Documented | Stops the teammate. | Teammate, Task |
| Path return | Documented | A command prints the path. An HTTP hook returns worktree. A failure fails the creation. | Worktree |
| Context only | Documented | additional. It cannot block. | Session, Subagent, Post, Setup, Notification |
| None | Documented | 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 | 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 | A blocking error on events that can block. | stderr is the reason. JSON fields are read but cannot override the block. Elicitation and Elicitation ignore them. |
| 1 | Documented | Not a block. | Exit 1 is a non-blocking error. Use exit 2 for policy. |
| Any other code | Documented | A non-blocking error. | The action goes on. The notice has the first line of stderr. JSON on stdout can still decide. |
| Invalid JSON | Documented | 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 and Claude Code still reads the JSON fields.
What Exit 2 Does per Event
| Effect | Status | Events |
|---|---|---|
| Blocks the action | Documented | Pre, User, User, Stop, Subagent, Teammate, Task, Task, Config (not policy_), Post, Pre, Pre, Elicitation, Elicitation |
| Fails the step | Documented | Worktree always. Worktree if the folder still exists. |
Shows stderr to Claude, no block | Documented | Post, Post |
Shows stderr to you, no block | Documented | Subagent, Session, Session, Cwd, File, Post, Post |
| Ignored | Documented | 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 | Success. The same as exit 0 with no output. |
| 2xx with a JSON object | Documented | Parsed like command JSON. A body that fails validation is a non-blocking error. |
| 2xx with other text, non-2xx, or a connection error | Documented | A non-blocking error. The text is not added to context. on blocks. |
How It Works
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).
The exit code 2 table was checked against the Hooks Reference. Not verified: the exact list of events for which the program honors continue and systemMessage.