SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f0634fb verified
|
Raw
History Blame Contribute Delete
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