钩子(Hooks)
在 17 个生命周期事件上执行 shell 命令,钩子还能放行、拦下或把动作改成问你。
钩子是在固定生命周期点上执行的 shell 命令,比如工具调用前、一轮结束后。用它可以把格式化、测试、通知这类动作接进 Klaus 的循环。
- 可挂的事件共 17 个:会话开始/结束、你发出提示词、工具调用前/后、工具失败、一轮停下、子代理开始/结束、压缩前/后、通知、Setup、任务创建/完成、工作树创建/移除。事件名写错的条目会被直接丢掉,不报错,所以看起来就像钩子没写一样。
- 存放位置:全局
~/.klaus/hooks.json,项目<项目>/.klaus/hooks.json。文件形状是「事件 → 若干组 → 每组一个可选 matcher 加一串钩子」,matcher 属于组而不是单条钩子。同一事件先跑全局再跑项目,同一文件内按书写顺序跑。 - 只有
type: "command"的条目会真的执行;同组里其他类型被跳过,不影响旁边的命令条目;缺command的同样跳过。timeout写的是秒,不写默认 30 秒;async: true的钩子不阻塞这一轮。 - 执行方式:引擎起一个
bash -c进程,把一份 JSON 从标准输入喂给它;如果标准输出是 JSON 就按结构解析。退出码非零或输出不是 JSON 时,这次钩子记为失败但不会拦住工具调用。 - 钩子不只是旁观者:工具调用前的钩子可以在输出里直接给出放行、拦下或「改成问你」,拦下时这次调用直接失败、连权限卡片都不会弹;提示词钩子可以挡掉你这一整条消息;停下钩子可以不让这一轮结束。多个钩子同时表态时,拦下最硬(第一个拦下的说了算),其次是改成问你,最后才是放行。
- 钩子清单在会话启动时随初始化一并交给引擎,引擎自己不读
hooks.json。在「设置 → 钩子」里信任、停用或改动钩子后,已经开着的会话会在下一回合自动重建并带上新清单;直接在编辑器里改hooks.json文件不会触发重建,要等下一次在应用里改动钩子或开新会话才生效。
还有一道信任门
写在 hooks.json 里不等于就会跑。每条钩子声明必须在「设置 → 钩子」里被你显式信任,才会注入会话,信任是按「作用域 + 文件路径 + 事件 + 组内位置 + 声明内容的哈希」记的。所以:
- 一条从没信任过的新声明,状态是「新钩子」,保持不执行。
- 一条你信任过、但内容之后被改动的声明,状态变成「内容已变更,需重新信任」,旧的信任作废、同样不执行——这是防的是某个技能的更新器悄悄改写它自带的钩子,然后靠你以前那一次批准继续跑代码。
- 你手动停用的声明状态是「已停用」;已经信任且内容未变的是「已信任」。
「设置 → 钩子」把钩子按来源分成三组:全局配置文件、插件、项目。每组里再按事件归拢,展开一个事件能看到每条钩子实际要跑的命令和它的状态;插件带来的钩子只显示事件和数量。可以单条信任,也可以「信任全部待审」。这也是「钩子明明写了却不跑」最常见的原因:不是事件名写错被丢,就是还没信任。另外,钩子执行失败不再往对话记录里塞内容,只写宿主日志。
- 插件带的钩子不走这条路:引擎直接从插件账本里加载它们(客户端如果也报一遍,每个插件钩子会每个事件触发两次)。
- 钩子只从
hooks.json读。从其他客户端导入时,写在settings.json的hooks键里的那些只是导入来源,它们本身不会被执行。