hooks.md +52 −20
587 587
588## Hook Output588## Hook Output
589 589
590590There are two ways for hooks to return output back to Claude Code. The outputThere are two mutually-exclusive ways for hooks to return output back to Claude Code. The output
591communicates whether to block and any feedback that should be shown to Claude591communicates whether to block and any feedback that should be shown to Claude
592and the user.592and the user.
593 593
595 595
596Hooks communicate status through exit codes, stdout, and stderr:596Hooks communicate status through exit codes, stdout, and stderr:
597 597
598598* **Exit code 0**: Success. `stdout` is shown to the user in transcript mode* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
599599 (CTRL-R), except for `UserPromptSubmit` and `SessionStart`, where stdout is (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
600600 added to the context. added to the context. JSON output in `stdout` is parsed for structured control
601601* **Exit code 2**: Blocking error. `stderr` is fed back to Claude to process (see [Advanced: JSON Output](#advanced-json-output)).
602602 automatically. See per-hook-event behavior below.* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
603603* **Other exit codes**: Non-blocking error. `stderr` is shown to the user and and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
604604 execution continues. is **not** processed for exit code 2. See per-hook-event behavior below.
605* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
606 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
607 it shows `No stderr output`. Execution continues.
605 608
606<Warning>609<Warning>
607 Reminder: Claude Code does not see stdout if the exit code is 0, except for610 Reminder: Claude Code does not see stdout if the exit code is 0, except for
624 627
625### Advanced: JSON Output628### Advanced: JSON Output
626 629
627630Hooks can return structured JSON in `stdout` for more sophisticated control:Hooks can return structured JSON in `stdout` for more sophisticated control.
631
632<Warning>
633 JSON output is only processed when the hook exits with code 0. If your hook
634 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
635 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
636</Warning>
628 637
629#### Common JSON Fields638#### Common JSON Fields
630 639
733 742
734#### `UserPromptSubmit` Decision Control743#### `UserPromptSubmit` Decision Control
735 744
736745`UserPromptSubmit` hooks can control whether a user prompt is processed.`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.
746
747**Adding context (exit code 0):**
748There are two ways to add context to the conversation:
749
7501. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added
751 as context. This is the easiest way to inject information.
752
7532. **JSON with `additionalContext`** (structured): Use the JSON format below for
754 more control. The `additionalContext` field is added as context.
737 755
738756* `"block"` prevents the prompt from being processed. The submitted prompt isBoth methods work with exit code 0. Plain stdout is shown as hook output in
739757 erased from context. `"reason"` is shown to the user but not added to context.the transcript; `additionalContext` is added more discretely.
740758* `undefined` allows the prompt to proceed normally. `"reason"` is ignored.
741759* `"hookSpecificOutput.additionalContext"` adds the string to the context if not**Blocking prompts:**
742760 blocked.
761* `"decision": "block"` prevents the prompt from being processed. The submitted
762 prompt is erased from context. `"reason"` is shown to the user but not added
763 to context.
764* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
743 765
744```json theme={null}766```json theme={null}
745{767{
752}774}
753```775```
754 776
777<Note>
778 The JSON format is not required for simple use cases. To add context, you can
779 just print plain text to stdout with exit code 0. Use JSON when you need to
780 block prompts or want more structured control.
781</Note>
782
755#### `Stop`/`SubagentStop` Decision Control783#### `Stop`/`SubagentStop` Decision Control
756 784
757`Stop` and `SubagentStop` hooks can control whether Claude must continue.785`Stop` and `SubagentStop` hooks can control whether Claude must continue.
845<Note>873<Note>
846 For `UserPromptSubmit` hooks, you can inject context using either method:874 For `UserPromptSubmit` hooks, you can inject context using either method:
847 875
848876 * Exit code 0 with stdout: Claude sees the context (special case for `UserPromptSubmit`) * **Plain text stdout** with exit code 0: Simplest approach—just print text
849877 * JSON output: Provides more control over the behavior * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
878 or `additionalContext` for structured context injection
879
880 Remember: Exit code 2 only uses `stderr` for the error message. To block using
881 JSON (with a custom reason), use `"decision": "block"` with exit code 0.
850</Note>882</Note>
851 883
852```python theme={null}884```python theme={null}
923 output = {955 output = {
924 "decision": "approve",956 "decision": "approve",
925 "reason": "Documentation file auto-approved",957 "reason": "Documentation file auto-approved",
926958 "suppressOutput": True # Don't show in transcript mode "suppressOutput": True # Don't show in verbose mode
927 }959 }
928 print(json.dumps(output))960 print(json.dumps(output))
929 sys.exit(0)961 sys.exit(0)
1036 * The `CLAUDE_CODE_REMOTE` environment variable indicates whether the hook is running in a remote (web) environment (`"true"`) or local CLI environment (not set or empty). Use this to run different logic based on execution context.1068 * The `CLAUDE_CODE_REMOTE` environment variable indicates whether the hook is running in a remote (web) environment (`"true"`) or local CLI environment (not set or empty). Use this to run different logic based on execution context.
1037* **Input**: JSON via stdin1069* **Input**: JSON via stdin
1038* **Output**:1070* **Output**:
10391071 * PreToolUse/PostToolUse/Stop/SubagentStop: Progress shown in transcript (Ctrl-R) * PreToolUse/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1040 * Notification/SessionEnd: Logged to debug only (`--debug`)1072 * Notification/SessionEnd: Logged to debug only (`--debug`)
1041 * UserPromptSubmit/SessionStart: stdout added as context for Claude1073 * UserPromptSubmit/SessionStart: stdout added as context for Claude
1042 1074
1085[DEBUG] Hook command completed with status 0: <Your stdout>1117[DEBUG] Hook command completed with status 0: <Your stdout>
1086```1118```
1087 1119
10881120Progress messages appear in transcript mode (Ctrl-R) showing:Progress messages appear in verbose mode (ctrl+o) showing:
1089 1121
1090* Which hook is running1122* Which hook is running
1091* Command being executed1123* Command being executed