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:
0means allow,2means 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.
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):
# 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:
{
"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:
{
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "Please use rg instead of grep"
}
}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) |
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:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs"
timeout = 5// 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.
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 — Full field reference for
[[hooks]]inconfig.toml - Agents and sub-agents — Use the
SubagentStopevent to trigger notifications after a sub-agent completes