Verboo Code 允许你接入一条 shell 命令,甚至是一次模型检查,让它在会话中每次发生特定事情时自动运行,不需要写插件,也不用指望代理会记得去做。这就是hook,整套配置都写在你的 settings.json 里。
Verboo Code 里的 hook 是什么?
hook 是 CLI 在特定事件发生时自动触发的命令,比如在调用某个工具之前、之后,或者会话开始的时候。你在 settings.json 中配置它,不需要插件,也不用记得手动运行任何东西。
Verboo Code 一共有27 种 hook 事件。日常最常用的几种:
| 事件 | 何时触发 |
|---|---|
PreToolUse | 任意工具执行之前 |
PostToolUse | 工具执行结束之后 |
PostToolUseFailure | 工具执行失败时 |
UserPromptSubmit | 你发送一条消息时 |
SessionStart | 打开、恢复或清空会话时 |
Stop | 代理即将结束这次回答之前 |
hook 应该写在哪个文件里?
根据你想要的生效范围,写在以下三个文件之一:
~/.claude/settings.json:对你个人生效,跨所有项目.claude/settings.json:只对这个项目生效,并会被提交进版本库,整个团队都会继承.claude/settings.local.json:只对这个项目生效,只对你个人,不进版本库
结构始终一样:一个 hooks 键,里面是事件名,一个可选的 matcher 用来按工具名过滤,以及匹配时要运行的 hook 列表:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{ "type": "command", "command": "..." }
]
}
]
}
}
如何让代理在编辑完文件后自动格式化代码?
这是 hook 最常见的用法:每次 Verboo Code 写入或编辑文件后自动运行格式化工具,不需要额外开口。把这段加进 settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "file=$(cat | jq -r '.tool_input.file_path'); npx prettier --write \"$file\" 2>/dev/null || true"
}
]
}
]
}
}
matcher 会作为正则表达式去匹配工具名,所以 Write|Edit 能同时命中两者。命令通过 stdin 接收该事件的 JSON 数据,而不是通过环境变量。在 PostToolUse 里,这个 JSON 包含 tool_name、tool_input(传给工具的内容)、tool_response(工具返回的内容)、session_id 和 cwd。这就是为什么示例用 jq 读取 file_path,而不是假设一个固定的文件名。
为什么 PostToolUse 上的 hook 拦不住工具执行?因为 PostToolUse 是在工具已经执行完之后才触发的。已经发生的事情没法拦截,只能做出反应:提醒模型、记录日志、触发修复。真正能拦住工具的是 PreToolUse,它的退出码行为非常明确,直接从源码核实过:
| 事件 | exit 0 | exit 2 | 其他退出码 |
|---|---|---|---|
PreToolUse | 正常继续 | 取消这次调用,stderr 会返回给模型 | 继续执行,但错误只显示给你看 |
PostToolUse | 正常继续 | 把 stderr 显示给模型,但工具已经执行完了 | 继续执行,错误只显示给你看 |
如果你需要阻止一次编辑发生,hook 必须写在 PreToolUse 上,并以 exit 2 结束。
如何让 hook 只对某一类命令生效,比如只对 git?
使用 if 字段,语法和你在 allow、deny 里用的权限规则一样,例如 Bash(git *)。它在 hook 进程被创建之前就完成过滤,不匹配的命令根本不会触发脚本。一个真实的例子:拦截直接推送到 main。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "cmd=$(cat | jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qE 'push.*(origin|upstream)[[:space:]]+main'; then echo '直接推送到 main 已被拦截,请开一个 PR。' >&2; exit 2; fi"
}
]
}
]
}
}
hook 一定得是 shell 脚本吗?
不一定。一共有 4 种类型,各自的用途不同:
| 类型 | 作用 | 消耗 token? |
|---|---|---|
command | 运行一条 shell 命令,bash 或 powershell | 不消耗 |
http | 把事件的 JSON 通过 POST 发到一个 URL | 不消耗 |
prompt | 用一个快速模型评估这次数据,回答是或否 | 消耗 |
agent | 运行一个完整的验证代理,模型由你自己选 | 消耗 |
举个例子,在 Stop 上配置一个 agent 类型的 hook,可以用来检查测试是否真的跑过并且通过了,而不是只相信主代理自己说完成了。
怎么查看当前会话里哪些 hook 是激活的?
运行 /hooks。它会按事件、再按 matcher 打开一个浏览界面,显示每个已配置的 hook 以及它的来源:用户配置、项目配置、本地配置、插件,还是会话本身。这个界面只能查看:要新建或修改 hook,你需要手动编辑 settings.json,或者直接让 Verboo Code 帮你改。旧的编辑界面只支持 command 类型的 hook,要在同一个菜单里同时支持全部 4 种类型,维护成本太高,所以现在官方的做法就是直接编辑配置文件。
prompt 和 agent 类型的 hook 每次触发都会调用一次模型,所以在按 token 计费的工具上,这是个需要节制使用的理由:每一次 PostToolUse、每一次 Stop、每一次检查都要花钱。在 Verboo Code 里,这类 hook 和会话里其他部分共用同一个不限量的 token 池,所以你可以在每一次 Stop 上都挂一个 agent 验证器,不用去算每次跑一遍要花多少钱。



