# Hooks

> Hooks（钩子）是一种自动触发机制：你预先告诉 Kimi Code CLI"每当发生 X，运行这个脚本"。脚本在你的本机执行，你可以在里面写任何逻辑。典型的使用场景：

- 网址：https://funcoding.ai/agents/kimi-code/customization/hooks/
- 来源：Kimi Code 官方文档原文（中文），MIT 许可，同步于 2026-10-11
- 官方原文：https://moonshotai.github.io/kimi-code/zh/customization/hooks.html

---
Hooks（钩子）是一种自动触发机制：你预先告诉 Kimi Code CLI"每当发生 X，运行这个脚本"。脚本在你的本机执行，你可以在里面写任何逻辑。典型的使用场景：

- **安全拦截**：Agent 要执行 Shell 命令前，检查是否包含危险操作（如 `rm -rf`），包含则阻断执行
- **桌面通知**：后台任务完成时，弹出系统通知提醒你回来查看结果
- **自动检查**：每次用户提交消息时，自动在上下文里附加一些背景信息（如当前 Git 分支）

## Hooks 是怎么工作的

配置一条 hook 规则，需要指定三件事：**在什么事件上触发**、**匹配哪些目标**、**运行哪个脚本**。

触发时，CLI 会把事件的详细信息（触发原因、工具名称、命令内容等）打包成 JSON，通过**标准输入**（stdin，程序运行时用来接收外部数据的通道）传给脚本。脚本读取这些信息后，决定怎么响应。

脚本的响应结果由两样东西决定：

- **退出码**（exit code，程序结束时向操作系统报告的状态数字）：`0` 表示放行，`2` 表示阻断，其他数字默认放行
- **标准输出**（stdout，脚本打印到终端的内容）：可以附带说明文字

即使脚本报错或超时，CLI 也**不会因此中断你的工作**。这种"出错就放行"的设计称为 fail-open（失败开放），避免 hook 异常阻塞主流程。

<div class="callout callout-warning">

**注意**

正因为 fail-open，Hooks 适合做提醒和轻量拦截，但**不应作为唯一的安全防线**。对真正高风险的操作，仍需依赖权限审批和人工确认。

</div>

## 快速上手：一个最简单的 hook

下面这条 hook 会在每次后台任务完成时，在终端标题栏闪一下通知（macOS 需要安装 `terminal-notifier`）：

```toml
# 写在 ~/.kimi-code/config.toml 里
[[hooks]]
event = "Notification"           # 触发时机：后台任务状态变化时
matcher = "task\\.completed"     # 只关心"已完成"的通知
command = "terminal-notifier -title Kimi -message 'Task done'"
```

保存配置、重开会话，下次后台任务完成时就会弹出通知。

## 配置

所有 hook 规则写在 `~/.kimi-code/config.toml` 的 `[[hooks]]` 数组里：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `event` | `string` | 是 | 触发事件名，取值见 [事件一览](#事件一览) |
| `matcher` | `string` | 否 | 用正则表达式（一种字符串匹配语法）过滤事件目标；不填则匹配全部 |
| `command` | `string` | 是 | 触发时要运行的 Shell 命令 |
| `timeout` | `integer` | 否 | 超时秒数，范围 1–600；默认 30 秒 |

`[[hooks]]` 只允许这四个字段，多写会导致配置文件加载失败。

**同一事件匹配多条规则时**，所有命中的 hook 并行运行；`command` 完全相同的多条规则只运行一次。

Hook 命令的工作目录是当前会话的项目目录。

<details>
<summary>进程组与超时处理</summary>

非 Windows 平台上，hook 进程运行在独立进程组中；超时后 CLI 先发送信号让脚本有机会善后，再强制终止。

</details>

### 事件数据格式

每次触发时，CLI 都会把以下基础信息通过 stdin 传给脚本：

```json
{
  "hook_event_name": "PreToolUse",
  "session_id": "session_abc",
  "session_title": "修复登录页",
  "client_type": "kimi_code_cli",
  "cwd": "/path/to/project"
}
```

具体事件还会附带额外字段（如工具名称、命令内容），见 [事件一览](#事件一览)。所有字段名使用下划线命名（snake_case）。

## 返回值

脚本结束后，CLI 根据退出码判断 hook 的意图：

| 退出码 | 含义 | CLI 怎么处理 |
| --- | --- | --- |
| `0` | 正常结束，放行 | 继续执行，若标准输出（stdout）有内容可附加到上下文 |
| `2` | 主动阻断 | 停止当前操作；错误输出（stderr，`console.error` 打印的内容）作为阻断原因 |
| 其他非零值 | 脚本出错 | 默认放行（fail-open） |
| 超时或崩溃 | 脚本异常 | 默认放行（fail-open） |

也可以通过标准输出返回一段 JSON 来阻断：

```json
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "请用 rg 代替 grep"
  }
}
```

<div class="callout callout-note">

**说明**

只有**可阻断事件**（`PreToolUse`、`Stop`、`UserPromptSubmit`）的返回值会影响主流程。其余事件属于**观察型事件**：触发后即发即忘，不管脚本返回什么，主流程都不会改变。

</div>

## 事件一览

| 事件 | Matcher 匹配的是 | 会触发阻断？ | 说明 |
| --- | --- | --- | --- |
| `UserPromptSubmit` | 用户提交的文本内容 | ✓ | 用户发送消息时触发；返回文本会附加到上下文，阻断则本轮不调用模型 |
| `UserPromptQueued` | 排队消息的文本内容 | — | 上一回合仍在运行、新消息进入队列时触发；payload 含 `prompt_id`、`prompt`、`queue_length` |
| `PreToolUse` | 工具名 | ✓ | 工具调用前、权限检查前触发；阻断后工具不会执行 |
| `Stop` | 空字符串 | ✓ | 模型准备结束本轮时触发；阻断后可追加一条消息让模型继续 |
| `TurnStarted` | 回合来源类型（如 `user`、`task`、`system_trigger`） | — | 新回合开始时触发；payload 含 `turn_id`、`origin_kind`、`origin_name`、`prompt` |
| `PostToolUse` | 工具名 | — | 工具成功执行后触发 |
| `PostToolUseFailure` | 工具名 | — | 工具失败或被阻断后触发 |
| `PermissionRequest` | 工具名 | — | 即将等待用户审批前触发 |
| `PermissionResult` | 工具名 | — | 审批结束后触发 |
| `SessionStart` | `startup` 或 `resume` | — | 新会话启动或历史会话恢复后触发；payload 含 `source`、`model`、`profile` |
| `SessionEnd` | `exit` 或 `archive` | — | 会话关闭后触发；`archive` 表示会话被归档而非退出 |
| `SessionHeartbeat` | 空字符串 | — | 会话存活期间每 60 秒触发一次，仅配置本事件时计时器才运行；payload 含 `uptime_ms` |
| `SubagentStart` | subagent 名称 | — | subagent 开始运行前触发 |
| `SubagentStop` | subagent 名称 | — | subagent 成功完成后触发 |
| `TaskStarted` | 任务类型（`agent`、`process` 或 `question`） | — | 后台任务启动时触发；payload 含 `task_id`、`description`、`detached` |
| `StopFailure` | 错误类型 | — | 本轮因错误失败后触发 |
| `Interrupt` | 空字符串 | — | 用户中断本轮时触发（如按 Esc）；超时等程序性中断不触发，此时 `Stop` 由本事件替代；payload 含 `reason` |
| `PreCompact` | `manual` 或 `auto` | — | 上下文压缩开始前触发；返回值被完全忽略 |
| `PostCompact` | `manual` 或 `auto` | — | 上下文压缩完成后触发 |
| `Notification` | 通知类型（如 `task.completed`） | — | 后台任务状态变化时触发 |

## 示例：阻断危险 Shell 命令

下面的 hook 在 Agent 调用 `Bash` 工具前检查命令内容，命中 `rm -rf` 时阻断：

```toml
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs"
timeout = 5
```

```js
// block-dangerous-bash.mjs
// 从 stdin 读取 CLI 传来的事件数据
let input = '';
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
  const payload = JSON.parse(input);         // 解析事件数据
  const command = payload.tool_input?.command ?? '';

  if (command.includes('rm -rf')) {
    // 通过 stderr 说明阻断原因，退出码 2 表示阻断
    console.error('检测到危险命令，已阻断');
    process.exit(2);
  }
  // 正常退出（退出码 0）表示放行
});
```

阻断后，Kimi Code CLI 会把阻断原因写回上下文，模型可以据此选择更安全的替代方案。

<div class="callout callout-warning">

**注意**

此示例仅演示阻断机制，不是生产级的安全解析器。真实场景更适合用白名单，或用专门的 Shell 解析器处理引号、变量展开和多段命令。

</div>

## 下一步

- [配置](#配置) — `[[hooks]]` 在 `config.toml` 中的完整字段声明
- [Agent 与 subagent](https://funcoding.ai/agents/kimi-code/customization/agents/) — 利用 `SubagentStop` 事件在 subagent 完成后触发通知
