61}61}
62```62```
63 63
64The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:64The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
65 65
66```bash theme={null}66```bash theme={null}
67#!/bin/bash67#!/bin/bash
69COMMAND=$(jq -r '.tool_input.command')69COMMAND=$(jq -r '.tool_input.command')
70 70
71if echo "$COMMAND" | grep -q 'rm -rf'; then71if echo "$COMMAND" | grep -q 'rm -rf'; then
72 echo '{"decision":"block","reason":"Destructive command blocked by hook"}'72 jq -n '{
73 hookSpecificOutput: {
74 hookEventName: "PreToolUse",
75 permissionDecision: "deny",
76 permissionDecisionReason: "Destructive command blocked by hook"
77 }
78 }'
73else79else
74 exit 0 # allow the command80 exit 0 # allow the command
75fi81fi
98 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:104 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
99 105
100 ```json theme={null}106 ```json theme={null}
101 { "decision": "block", "reason": "Destructive command blocked by hook" }107 {
108 "hookSpecificOutput": {
109 "hookEventName": "PreToolUse",
110 "permissionDecision": "deny",
111 "permissionDecisionReason": "Destructive command blocked by hook"
112 }
113 }
102 ```114 ```
103 115
104 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.116 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
403 415
404The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.416The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
405 417
406**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.418**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output). JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.
407 419
408**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.420**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.
409 421
445 457
446### JSON output458### JSON output
447 459
448You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.460Exit codes let you allow or block, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including [decision control](#decision-control) for blocking, allowing, or escalating to the user.
449 461
450Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.462<Note>
463 You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.
464</Note>
451 465
452Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.466Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.
453 467
454The JSON object has two parts:468The JSON object supports three kinds of fields:
455 469
456* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.470* **Universal fields** like `continue` work across all events. These are listed in the table below.
457* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.471* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
472* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
458 473
459| Field | Default | Description |474| Field | Default | Description |
460| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |475| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
461| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |476| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
462| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |477| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
463| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |478| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
464| `systemMessage` | none | Warning message shown to the user |479| `systemMessage` | none | Warning message shown to the user |
465 480
466This example uses a top-level field to stop Claude:481To stop Claude entirely regardless of event type:
467 482
468```json theme={null}483```json theme={null}
469{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }484{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
470```485```
471 486
472This example uses `hookSpecificOutput` to deny a PreToolUse tool call:487#### Decision control
473 488
474```json theme={null}489Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:
475{490
491| Events | Decision pattern | Key fields |
492| :-------------------------------------------------------------------- | :------------------- | :---------------------------------------------------------------- |
493| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop | Top-level `decision` | `decision: "block"`, `reason` |
494| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
495| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
496
497Here are examples of each pattern in action:
498
499<Tabs>
500 <Tab title="Top-level decision">
501 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, and `SubagentStop`. The only value is `"block"` — to allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
502
503 ```json theme={null}
504 {
505 "decision": "block",
506 "reason": "Test suite must pass before proceeding"
507 }
508 ```
509 </Tab>
510
511 <Tab title="PreToolUse">
512 Uses `hookSpecificOutput` for richer control: allow, deny, or escalate to the user. You can also modify tool input before it runs or inject additional context for Claude. See [PreToolUse decision control](#pretooluse-decision-control) for the full set of options.
513
514 ```json theme={null}
515 {
476 "hookSpecificOutput": {516 "hookSpecificOutput": {
477 "hookEventName": "PreToolUse",517 "hookEventName": "PreToolUse",
478 "permissionDecision": "deny",518 "permissionDecision": "deny",
479 "permissionDecisionReason": "Database writes are not allowed"519 "permissionDecisionReason": "Database writes are not allowed"
480 }520 }
481}521 }
482```522 ```
523 </Tab>
524
525 <Tab title="PermissionRequest">
526 Uses `hookSpecificOutput` to allow or deny a permission request on behalf of the user. When allowing, you can also modify the tool's input or apply permission rules so the user isn't prompted again. See [PermissionRequest decision control](#permissionrequest-decision-control) for the full set of options.
527
528 ```json theme={null}
529 {
530 "hookSpecificOutput": {
531 "hookEventName": "PermissionRequest",
532 "decision": {
533 "behavior": "allow",
534 "updatedInput": {
535 "command": "npm run lint"
536 }
537 }
538 }
539 }
540 ```
541 </Tab>
542</Tabs>
483 543
484For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).544For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).
485 545
739 799
740#### PreToolUse decision control800#### PreToolUse decision control
741 801
742`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:802`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: three outcomes (allow, deny, or ask) plus the ability to modify tool input before execution.
743 803
744| Field | Description |804| Field | Description |
745| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |805| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
763```823```
764 824
765<Note>825<Note>
766 The `decision` and `reason` fields are deprecated for PreToolUse hooks.826 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.
767 Use `hookSpecificOutput.permissionDecision` and
768 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
769 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
770</Note>827</Note>
771 828
772### PermissionRequest829### PermissionRequest