297| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |297| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
298| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |298| `type` | yes | `"command"`, `"http"`, `"mcp_tool"`, `"prompt"`, or `"agent"` |
299| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |299| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, and `PermissionDenied`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |
300| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |300| `timeout` | no | Seconds before canceling. Defaults: 600 for `command`, `http`, and `mcp_tool`; 30 for `prompt`; 60 for `agent`. [`UserPromptSubmit`](#userpromptsubmit) lowers the `command`, `http`, and `mcp_tool` default to 30 |
301| `statusMessage` | no | Custom spinner message displayed while the hook runs |301| `statusMessage` | no | Custom spinner message displayed while the hook runs |
302| `once` | no | If `true`, runs once per session then is removed. Only honored for hooks declared in [skill frontmatter](#hooks-in-skills-and-agents); ignored in settings files and agent frontmatter |302| `once` | no | If `true`, runs once per session then is removed. Only honored for hooks declared in [skill frontmatter](#hooks-in-skills-and-agents); ignored in settings files and agent frontmatter |
303 303
558 558
559Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.559Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.
560 560
561On macOS and Linux, command hooks run in their own session without a controlling terminal as of v2.1.139. The hook process and any child processes cannot open `/dev/tty` or send escape sequences directly to the Claude Code interface. Windows has no `/dev/tty`. To surface a message to the user on any platform, return [`systemMessage`](#json-output) in JSON output. To trigger a desktop notification, set a window title, or ring the bell, return [`terminalSequence`](#emit-terminal-notifications) instead.
562
561### Common input fields563### Common input fields
562 564
563Hook events receive these fields as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.565Hook events receive these fields as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.
685 687
686Your 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.688Your 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.
687 689
688Hook output injected into context (`additionalContext`, `systemMessage`, or plain stdout) is capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way large tool results are handled.690Hook output strings, including `additionalContext`, `systemMessage`, and plain stdout, are capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way large tool results are handled.
689 691
690The JSON object supports three kinds of fields:692The JSON object supports three kinds of fields:
691 693
694* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.696* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
695 697
696| Field | Default | Description |698| Field | Default | Description |
697| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |699| :----------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
698| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |700| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
699| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |701| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
700| `suppressOutput` | `false` | If `true`, omits stdout from the debug log |702| `suppressOutput` | `false` | If `true`, omits stdout from the debug log |
701| `systemMessage` | none | Warning message shown to the user |703| `systemMessage` | none | Warning message shown to the user |
704| `terminalSequence` | none | A terminal escape sequence for Claude Code to emit on your behalf, such as a desktop notification, window title, or bell. Restricted to OSC `0`/`1`/`2`/`9`/`99`/`777` and BEL. If the value contains anything outside the allowlist, the field is ignored. Use this instead of writing to `/dev/tty`, which is unavailable to hooks |
702 705
703To stop Claude entirely regardless of event type:706To stop Claude entirely regardless of event type:
704 707
706{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }709{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
707```710```
708 711
712#### Emit terminal notifications
713
714The `terminalSequence` field requires Claude Code v2.1.141 or later.
715
716Hooks run without a controlling terminal, so writing escape sequences directly to `/dev/tty` fails. Instead, return the escape sequence in the `terminalSequence` field and Claude Code emits it for you through its own terminal write path. This is race-free, works inside tmux and GNU screen, and works on Windows where there is no `/dev/tty`.
717
718The field accepts a string of one or more allowlisted escape sequences:
719
720* OSC `0`, `1`, `2`: window and icon titles
721* OSC `9`: iTerm2, ConEmu, Windows Terminal, and WezTerm notifications, including `9;4` taskbar progress
722* OSC `99`: Kitty notifications
723* OSC `777`: urxvt, Ghostty, and Warp notifications
724* Bare BEL
725
726Sequences may be terminated with BEL or with ST. Anything outside the allowlist, including CSI cursor and color sequences, OSC palette sequences, OSC 8 hyperlinks, OSC 52 clipboard writes, and OSC 1337, is rejected and the field is ignored.
727
728The example below fires a desktop notification from a `Notification` hook. The escape sequence is built with `printf` octal escapes so the control bytes never appear on the shell command line, and `jq -n --arg` builds the JSON output so quotes, backslashes, and newlines in the notification message are escaped correctly:
729
730```bash theme={null}
731#!/bin/bash
732# Notification hook: ping the desktop when Claude Code needs attention.
733input=$(cat)
734title="Claude Code"
735body=$(jq -r '.message // "Needs your attention"' <<<"$input")
736seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
737jq -nc --arg seq "$seq" '{terminalSequence: $seq}'
738```
739
740The `{ "terminalSequence": "..." }` shape is the same from any shell or language. On Windows, build the escape string in PowerShell or a script and emit the same JSON object.
741
742<Note>
743 `terminalSequence` is the supported replacement for hooks that previously wrote escape sequences directly to `/dev/tty`. The allowlist is restricted to sequences that cannot move the cursor or alter colors, so a hook can never corrupt an on-screen prompt.
744</Note>
745
709#### Add context for Claude746#### Add context for Claude
710 747
711The `additionalContext` field passes a string from your hook into Claude's context window. Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.748The `additionalContext` field passes a string from your hook into Claude's context window. Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.
991to add additional context based on the prompt/conversation, validate prompts, or1028to add additional context based on the prompt/conversation, validate prompts, or
992block certain types of prompts.1029block certain types of prompts.
993 1030
1031`UserPromptSubmit` hooks have a default timeout of 30 seconds for `command`, `http`, and `mcp_tool` types, shorter than the 600-second default for those types on other events. Because this hook runs before every prompt and blocks model processing until it completes, a stuck hook stalls the session. If your hook needs more time, set the `timeout` field in the hook entry.
1032
994#### UserPromptSubmit input1033#### UserPromptSubmit input
995 1034
996In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.1035In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
2604 2643
2605When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.2644When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.
2606 2645
2607After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.2646After the background process exits, if the hook produced a JSON response with an `additionalContext` field, that content is delivered to Claude as context on the next conversation turn. A `systemMessage` field is shown to you, not to Claude.
2608 2647
2609Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.2648Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
2610 2649
2625 exit 02664 exit 0
2626fi2665fi
2627 2666
2628# Run tests and report results via systemMessage2667# Run tests and report results to Claude via additionalContext
2629RESULT=$(npm test 2>&1)2668RESULT=$(npm test 2>&1)
2630EXIT_CODE=$?2669EXIT_CODE=$?
2631 2670
2632if [ $EXIT_CODE -eq 0 ]; then2671if [ $EXIT_CODE -eq 0 ]; then
2633 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"2672 MSG="Tests passed after editing $FILE_PATH"
2634else2673else
2635 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"2674 MSG="Tests failed after editing $FILE_PATH: $RESULT"
2636fi2675fi
2676jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'
2637```2677```
2638 2678
2639Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:2679Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run: