kimi-code/docs/zh/customization/hooks.md
2026-05-22 15:54:50 +08:00

8.2 KiB
Raw Permalink Blame History

Hooks

Hooks 让你在 Kimi Code CLI 的关键生命周期点运行本地命令。它适合做轻量的策略检查、审计记录、桌面通知或与本地自动化脚本联动,例如在危险工具调用前拦截,或在后台子 Agent 完成后触发通知。

Hook 命令在本地 Shell 中运行Kimi Code CLI 会把事件 payload 以 JSON 写入命令的 stdin。命令的 stdout、stderr 和退出码决定 hook 的结果除明确阻断的情况外hook 失败时默认放行fail-open不会让主流程因为脚本异常而中断。

::: warning 注意 Hooks 适合做本地提醒和轻量拦截不应作为唯一安全边界。脚本报错、超时或返回普通非零退出码时会默认放行fail-open高风险工具调用仍应依赖权限审批和人工确认。 :::

配置

~/.kimi-code/config.toml 中使用 [[hooks]] 数组表声明 hook

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

[[hooks]]
event = "Notification"
matcher = "task\\.completed"
command = "terminal-notifier -title Kimi -message 'Background task finished'"

字段含义如下:

字段 类型 必填 说明
event string 事件名,取值必须是下文「事件」表中的某一项;其他值会让整份配置加载失败
matcher string 用于匹配事件目标的正则表达式;缺省或空字符串表示匹配全部
command string 要运行的 Shell 命令,长度不能为零
timeout integer 超时时间,单位秒,范围 1600未设置时默认为 30 秒

每个 [[hooks]] 表只允许出现这四个字段,写错或多写字段会导致配置文件解析失败。

同一次事件触发时,命中的多个 hook 会并行运行;如果多个配置项的 command 完全相同,只会运行一次。matcher 使用 JavaScript 正则表达式语义;非法正则会被静默跳过,等同于不匹配。

Hook 命令通过 Shell 启动(等价于 sh -c <command>),子进程的工作目录就是当前会话的 cwd。在非 Windows 平台上,子进程会被放入独立的进程组,超时或会话被中断时会先发送 SIGTERM、100 毫秒后再发送 SIGKILL,确保 hook 内部 fork 出的子进程也能被一并清理。

传给 hook 的 JSON 字段统一使用 snake_case。每个 payload 都包含:

{
  "hook_event_name": "PreToolUse",
  "session_id": "session_abc",
  "cwd": "/path/to/project"
}

其余字段由事件类型决定,见下文事件表。

返回值

Hook 命令的退出码和 stdout 会被解释为以下结果:

结果 行为
退出码 0 放行;如果 stdout 是 JSON可从 messagehookSpecificOutput.message 读取文本
退出码 2 阻断stderr 会作为阻断原因
其他非零退出码 默认放行fail-open
超时或进程异常 默认放行fail-open

当 stdout 是 JSON并且 hookSpecificOutput.permissionDecisiondeny 时,也会被视为阻断:

{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead"
  }
}

阻断只对支持控制流的事件生效。例如 PreToolUse 可以阻断工具调用,Stop 可以让当前轮次追加一次继续消息。观察型事件(例如 PostToolUsePostToolUseFailurePostCompactSubagentStopStopFailureNotification以「即发即忘fire-and-forget」方式异步触发返回值被忽略不会改变主流程。PreCompact 使用 trigger(而非 triggerBlock)调用,返回值同样被完全忽略,不属于可阻断事件。

阻断生效时,如果脚本未通过 stderr 或 JSON 输出提供原因CLI 会回退到 Blocked by <event> hook 作为占位原因。PreToolUse 阻断会作为工具失败结果写回上下文,模型可以根据原因选择替代方案。

事件

当前会自动触发的事件如下:

事件 Matcher 主要 payload 行为
UserPromptSubmit 用户提交的文本内容 promptContentPart[] 数组) 仅对真实 User 消息触发。hook 返回的文本会包裹为 hook 结果,写入会话历史用于 transcript/replay并展示给用户当前 LLM 轮次会继续,但不会把 hook 结果发给模型;若 hook 阻断,阻断原因会作为 Assistant 消息返回给用户,且不再调用模型;若所有 hook 均无输出,正常 LLM 轮次继续
PreToolUse 工具名 tool_nametool_inputtool_call_id 在权限检查前触发;阻断后工具不会执行
PostToolUse 工具名 tool_nametool_inputtool_call_idtool_output 工具成功后触发;tool_output 被截断至前 2000 个字符
PostToolUseFailure 工具名 tool_nametool_inputtool_call_iderror 工具失败或被 hook 阻断后触发
Stop 空字符串 stop_hook_active 模型准备停止时触发;阻断后会把原因直接作为系统触发的 User 消息追加进上下文,并最多继续一次
StopFailure 错误类型 error_typeerror_message 当前轮次因非取消错误失败后触发
SessionStart startupresume source 新会话主 Agent 创建后,或历史会话恢复完成后触发
SessionEnd exit reason 会话关闭并 flush 元数据后触发
SubagentStart 子 Agent 名称 agent_nameprompt 子 Agent 配置完成、真正开始运行前触发;prompt 被截断至前 500 个字符
SubagentStop 子 Agent 名称 agent_nameresponse 子 Agent 成功完成后异步触发,失败时不触发;response 被截断至前 500 个字符
PreCompact manualauto triggertoken_count 上下文压缩真正开始前触发;此事件使用 trigger(非 triggerBlock)调用,返回值被完全忽略,阻断决策不会被读取
PostCompact manualauto triggerestimated_token_count 上下文压缩成功写入后异步触发;阻断结果不会改变主流程
Notification 通知类型 sinknotification_typetitlebodyseveritysource_kindsource_id 当前在后台子 Agent 结果写入上下文时触发;notification_type 取值为 task.completedtask.failedtask.killedtask.lostsink 为 context

UserPromptSubmit 的返回文本会被包裹成一条 hook 结果:

<hook_result hook_event="UserPromptSubmit">
hook response
</hook_result>

如果多个 UserPromptSubmit hook 返回文本,每个结果都会拥有独立的 <hook_result> 标签。这条消息会带有 hook 结果来源,用于 transcript/replay但不会发给模型。模型只看到原始用户输入当前轮次继续。

如果 UserPromptSubmit hook 阻断请求,阻断原因会使用同样格式返回给用户,但本轮不会继续请求模型。

Stop 的阻断原因会直接作为系统触发的 User 消息写入上下文,让当前轮次继续:

continue from hook

示例:阻断危险 Shell 命令

下面的 hook 会在 Bash 工具调用前读取 stdin 中的 tool_input.command。如果命令包含 rm -rf,脚本以退出码 2 结束并把原因写到 stderr

::: warning 注意 这个示例只演示 hook 如何阻断工具调用,不是完整的 Shell 安全解析器。真实策略更适合使用 allowlist或用专门的 Shell 解析逻辑处理引号、变量展开、别名和多段命令。 :::

[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs"
timeout = 5
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')) {
    console.error('Blocked dangerous shell command');
    process.exit(2);
  }
});

当 hook 阻断工具调用时Kimi Code CLI 会把阻断原因作为工具失败结果写回上下文,模型可以据此选择更安全的替代方案。