mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-20 22:26:23 +00:00
Fires an observation-only Interrupt event when a turn is aborted by the user (e.g. pressing Esc). Previously neither Stop nor StopFailure fired on interrupt, so external tooling that tracks status from hooks stayed stuck on a working state.
157 lines
8.4 KiB
Markdown
157 lines
8.4 KiB
Markdown
# 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 entries in the "Event Reference" table below |
|
||
| `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. On non-Windows platforms, hook processes are placed in a separate process group; on timeout, a signal is sent first to give the process a chance to clean up, then it is forcibly terminated.
|
||
|
||
### 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",
|
||
"cwd": "/path/to/project"
|
||
}
|
||
```
|
||
|
||
Specific events will also include additional fields (such as tool name and command content); see the event reference below. 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; 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; if blocked, the model is not called for this turn |
|
||
| `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 current turn; if blocked, a message can be appended to let the model continue |
|
||
| `PostToolUse` | Tool name | — | Triggered after a tool executes successfully (observation only) |
|
||
| `PostToolUseFailure` | Tool name | — | Triggered after a tool fails or is blocked (observation only) |
|
||
| `PermissionRequest` | Tool name | — | Triggered just before waiting for user approval (observation only) |
|
||
| `PermissionResult` | Tool name | — | Triggered after approval completes (observation only) |
|
||
| `SessionStart` | `startup` or `resume` | — | Triggered after a new session starts or a previous session resumes |
|
||
| `SessionEnd` | `exit` | — | Triggered after a session closes |
|
||
| `SubagentStart` | Sub-agent name | — | Triggered before a sub-agent starts running |
|
||
| `SubagentStop` | Sub-agent name | — | Triggered after a sub-agent completes successfully (observation only) |
|
||
| `StopFailure` | Error type | — | Triggered after the current turn fails due to an error (observation only) |
|
||
| `Interrupt` | Empty string | — | Triggered when the user interrupts the current turn (e.g. pressing Esc); not fired for timeouts or other programmatic aborts. `Stop` does not fire on interrupts, so this event fires instead. The payload includes a `reason` field (observation only) |
|
||
| `PreCompact` | `manual` or `auto` | — | Triggered before context compaction begins; return values are completely ignored |
|
||
| `PostCompact` | `manual` or `auto` | — | Triggered after context compaction completes (observation only) |
|
||
| `Notification` | Notification type (e.g. `task.completed`) | — | Triggered when a background task status changes (observation only) |
|
||
|
||
## 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 — it 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 files](../configuration/config-files.md#hooks) — 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
|