Skip to content

Hooks

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

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

注意

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

配置

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

toml
[[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'"

字段含义如下:

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

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

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

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

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

json
{
  "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 时,也会被视为阻断:

json
{
  "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当前轮次因非取消错误失败后触发
SessionStartstartupresumesource新会话主 Agent 创建后,或历史会话恢复完成后触发
SessionEndexitreason会话关闭并 flush 元数据后触发
SubagentStart子 Agent 名称agent_nameprompt子 Agent 配置完成、真正开始运行前触发;prompt 被截断至前 500 个字符
SubagentStop子 Agent 名称agent_nameresponse子 Agent 成功完成后异步触发,失败时不触发;response 被截断至前 500 个字符
PreCompactmanualautotriggertoken_count上下文压缩真正开始前触发;此事件使用 trigger(非 triggerBlock)调用,返回值被完全忽略,阻断决策不会被读取
PostCompactmanualautotriggerestimated_token_count上下文压缩成功写入后异步触发;阻断结果不会改变主流程
Notification通知类型sinknotification_typetitlebodyseveritysource_kindsource_id当前在后台子 Agent 结果写入上下文时触发;notification_type 取值为 task.completedtask.failedtask.killedtask.lost,sink 为 context

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

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

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

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

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

text
continue from hook

示例:阻断危险 Shell 命令

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

注意

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

toml
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs"
timeout = 5
js
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 会把阻断原因作为工具失败结果写回上下文,模型可以据此选择更安全的替代方案。