|
Download docs/en/customization/hooks.md from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 9.24 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/docs/en/customization/hooks.md
- Command line
-
hf download hf://SaylorTwift/kimi-code/docs/en/customization/hooks.md
-
curl -L -o hooks.md https://huggingface.co/SaylorTwift/kimi-code/resolve/main/docs/en/customization/hooks.md
9.24 kB
| # Hooks | |
| Hooks are an automatic trigger mechanism: you tell Kimi Code CLI in advance "whenever X happens, run this script." The script runs on your local machine, and you can put any logic inside it. Typical use cases: | |
| - **Security interception**: Before the Agent executes a shell command, check whether it contains dangerous operations (such as `rm -rf`) and block execution if so | |
| - **Desktop notifications**: When a background task completes, pop up a system notification to bring you back to review the results | |
| - **Automatic checks**: Each time the user submits a message, automatically append some background information to the context (such as the current Git branch) | |
| ## How Hooks Work | |
| Configuring a hook rule requires specifying three things: **which event to trigger on**, **which targets to match**, and **which script to run**. | |
| When triggered, the CLI packages the event's details (trigger reason, tool name, command content, etc.) into JSON and passes it to your script via **standard input** (stdin). The script reads this information and decides how to respond. | |
| The script's response is determined by two things: | |
| - **Exit code**: `0` means allow, `2` means block, other non-zero values default to allow | |
| - **Standard output** (stdout): can include explanatory text | |
| Even if the script errors or times out, the CLI **will not interrupt your work** as a result. This "allow on failure" design is called fail-open, preventing hook errors from becoming blockers. | |
| ::: warning Note | |
| Precisely because of fail-open, Hooks are suitable for alerts and lightweight interception, but **should not be used as the sole security barrier**. For truly high-risk operations, rely on permission approvals and manual confirmation. | |
| ::: | |
| ## Quick Start: A Minimal Hook | |
| The following hook flashes a notification in the terminal title bar each time a background task completes (macOS requires `terminal-notifier` to be installed): | |
| ```toml | |
| # Written in ~/.kimi-code/config.toml | |
| [[hooks]] | |
| event = "Notification" # Trigger: when a background task status changes | |
| matcher = "task\\.completed" # Only care about "completed" notifications | |
| command = "terminal-notifier -title Kimi -message 'Task done'" | |
| ``` | |
| Save the config, start a new session, and a notification will appear the next time a background task completes. | |
| ## Configuration | |
| All hook rules are written in the `[[hooks]]` array in `~/.kimi-code/config.toml`, where each entry is one rule: | |
| | Field | Type | Required | Description | | |
| | --- | --- | --- | --- | | |
| | `event` | `string` | Yes | Trigger event name; must be one of the events in the [event reference](#event-reference) | | |
| | `matcher` | `string` | No | A regular expression to filter event targets; if omitted, matches all | | |
| | `command` | `string` | Yes | The shell command to run when triggered | | |
| | `timeout` | `integer` | No | Timeout in seconds, range 1β600; defaults to 30 seconds | | |
| `[[hooks]]` only allows these four fields; extra fields will cause the config file to fail to load. | |
| **When multiple rules match the same event**, all matching hooks run in parallel; multiple rules with identical `command` values run only once. | |
| The working directory for hook commands is the current session's project directory. | |
| <details> | |
| <summary>Process group and timeout handling</summary> | |
| On non-Windows platforms, hook processes run in a separate process group; on timeout, the CLI first sends a signal to give the script a chance to clean up, then forcibly terminates it. | |
| </details> | |
| ### Event Data Format | |
| Each time a hook triggers, the CLI passes the following base information to the script via stdin: | |
| ```json | |
| { | |
| "hook_event_name": "PreToolUse", | |
| "session_id": "session_abc", | |
| "session_title": "Fix the login page", | |
| "client_type": "kimi_code_cli", | |
| "cwd": "/path/to/project" | |
| } | |
| ``` | |
| Specific events will also include additional fields (such as tool name and command content); see the [event reference](#event-reference). All field names use snake_case. | |
| ## Return Values | |
| After the script exits, the CLI determines the hook's intent based on the exit code: | |
| | Exit code | Meaning | CLI behavior | | |
| | --- | --- | --- | | |
| | `0` | Normal exit, allow | Continue execution; stdout content (if any) may be appended to context | | |
| | `2` | Intentional block | Stop the current operation; stderr content (printed via `console.error`) is used as the reason for blocking | | |
| | Other non-zero | Script error | Default allow (fail-open) | | |
| | Timeout or crash | Script exception | Default allow (fail-open) | | |
| You can also return a JSON object via stdout to block: | |
| ```json | |
| { | |
| "hookSpecificOutput": { | |
| "permissionDecision": "deny", | |
| "permissionDecisionReason": "Please use rg instead of grep" | |
| } | |
| } | |
| ``` | |
| ::: info Which events support blocking? | |
| Only **blockable events** (`PreToolUse`, `Stop`, `UserPromptSubmit`) have return values that affect the main flow. All other events are **observation-only events**: they fire and forget, and the main flow is unaffected regardless of what the script returns. | |
| ::: | |
| ## Event Reference | |
| | Event | Matcher matches | Supports blocking? | Description | | |
| | --- | --- | --- | --- | | |
| | `UserPromptSubmit` | The text submitted by the user | β | Triggered when the user sends a message; returned text is appended to context; blocking skips the model call this turn | | |
| | `UserPromptQueued` | The queued prompt text | β | Triggered when a message is queued while a turn is still running; payload includes `prompt_id`, `prompt`, `queue_length` | | |
| | `PreToolUse` | Tool name | β | Triggered before a tool call (before permission checks); the tool will not execute if blocked | | |
| | `Stop` | Empty string | β | Triggered when the model is about to end the turn; if blocked, a message can be appended to let the model continue | | |
| | `TurnStarted` | Turn origin kind (e.g. `user`, `task`, `system_trigger`) | β | Triggered when a new turn begins; payload includes `turn_id`, `origin_kind`, `origin_name`, `prompt` | | |
| | `PostToolUse` | Tool name | β | Triggered after a tool executes successfully | | |
| | `PostToolUseFailure` | Tool name | β | Triggered after a tool fails or is blocked | | |
| | `PermissionRequest` | Tool name | β | Triggered just before waiting for user approval | | |
| | `PermissionResult` | Tool name | β | Triggered after approval completes | | |
| | `SessionStart` | `startup` or `resume` | β | Triggered after a session starts or resumes; payload includes `source`, `model`, `profile` | | |
| | `SessionEnd` | `exit` or `archive` | β | Triggered after a session closes; `archive` means the session was archived rather than exited | | |
| | `SessionHeartbeat` | Empty string | β | Triggered every 60 seconds while the session is alive; the timer runs only when this event is configured; payload includes `uptime_ms` | | |
| | `SubagentStart` | Sub-agent name | β | Triggered before a sub-agent starts running | | |
| | `SubagentStop` | Sub-agent name | β | Triggered after a sub-agent completes successfully | | |
| | `TaskStarted` | Task kind (`agent`, `process`, or `question`) | β | Triggered when a background task starts; payload includes `task_id`, `description`, `detached` | | |
| | `StopFailure` | Error type | β | Triggered after the current turn fails due to an error | | |
| | `Interrupt` | Empty string | β | Triggered when the user interrupts the turn (e.g. pressing Esc); not fired for timeouts or programmatic aborts; fires in place of `Stop`; payload includes `reason` | | |
| | `PreCompact` | `manual` or `auto` | β | Triggered before context compaction begins; return values are completely ignored | | |
| | `PostCompact` | `manual` or `auto` | β | Triggered after context compaction completes | | |
| | `Notification` | Notification type (e.g. `task.completed`) | β | Triggered when a background task status changes | | |
| ## Example: Blocking Dangerous Shell Commands | |
| The following hook checks the command content before the Agent calls the `Bash` tool and blocks it if `rm -rf` is detected: | |
| ```toml | |
| [[hooks]] | |
| event = "PreToolUse" | |
| matcher = "Bash" | |
| command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs" | |
| timeout = 5 | |
| ``` | |
| ```js | |
| // block-dangerous-bash.mjs | |
| // Read event data passed by the CLI from stdin | |
| let input = ''; | |
| process.stdin.on('data', (chunk) => { input += chunk; }); | |
| process.stdin.on('end', () => { | |
| const payload = JSON.parse(input); // Parse event data | |
| const command = payload.tool_input?.command ?? ''; | |
| if (command.includes('rm -rf')) { | |
| // Explain the blocking reason via stderr; exit code 2 means block | |
| console.error('Dangerous command detected, blocked'); | |
| process.exit(2); | |
| } | |
| // Normal exit (exit code 0) means allow | |
| }); | |
| ``` | |
| After blocking, Kimi Code CLI writes the blocking reason back into the context, and the model can use this to choose a safer alternative. | |
| ::: warning Note | |
| This example only demonstrates the blocking mechanism and is not a production-grade security parser. Real scenarios are better served by whitelists, or a dedicated shell parser to handle quoting, variable expansion, and multi-command sequences. | |
| ::: | |
| ## Next steps | |
| - [Configuration](#configuration) β Full field reference for `[[hooks]]` in `config.toml` | |
| - [Agents and sub-agents](./agents.md) β Use the `SubagentStop` event to trigger notifications after a sub-agent completes | |