深夜里一个任务失败了,没有人盯着终端:目前在 Verboo Code 中,它就会一直停在失败状态,直到有人回来手动重新运行。Hook Chains 正是为了解决这个问题。它是一个事件驱动的恢复层:当失败 hook 触发时,评估声明式规则并执行一个动作,比如用新代理恢复任务,或者通知团队。这是一个真实存在的功能,默认关闭。本文展示开启它的确切命令和完整的配置文件,全部直接在 verbeux-ai/code 源码中核实过(src/utils/hookChains.ts、docs/hook-chains.md)。
什么会触发一次 hook chain?
目前是两种事件。PostToolUseFailure,当一个 tool 在执行过程中失败时;以及 outcome 为 "failed" 的 TaskCompleted,当一个任务未成功结束时。每条规则声明它响应哪个事件和哪个结果;结果可以用 outcome 填一个单值,或用 outcomes 填一个列表(两者不能同时使用,schema 本身会拒绝)。
我要怎么在 Verboo Code 里开启 hook chains?
需要两步。第一步,开启整个功能的环境变量,因为即使存在配置文件,它默认也是关闭的:
export CLAUDE_CODE_ENABLE_HOOK_CHAINS=1
# 也接受: true, yes, on
第二步,规则文件。默认路径是项目根目录下的 .verboo/hook-chains.json。要使用其他路径:
export CLAUDE_CODE_HOOK_CHAINS_CONFIG_PATH=/path/to/hook-chains.json
没有这个激活变量,Verboo Code 甚至不会去读取文件:enabled 的检查发生在任何从磁盘加载配置的尝试之前。
配置文件的格式是什么样的?
一个对象,包含四个通用控制字段和一个规则列表。它可以直接放在 JSON 根部,也可以包在一个 hookChains 键里,两种写法都可以。
| 字段 | 类型 | 默认值 |
|---|---|---|
version | 数字 | 1 |
enabled | 布尔值 | true |
maxChainDepth | 整数,1 到 10 | 2 |
defaultCooldownMs | 整数 | 30000 |
defaultDedupWindowMs | 整数 | 30000 |
rules | 列表 | [] |
一个最简示例,用新代理恢复一个失败的任务:
{
"version": 1,
"enabled": true,
"maxChainDepth": 2,
"defaultCooldownMs": 30000,
"defaultDedupWindowMs": 30000,
"rules": [
{
"id": "retry-task-via-fallback",
"trigger": { "event": "TaskCompleted", "outcome": "failed" },
"cooldownMs": 60000,
"actions": [
{
"type": "spawn_fallback_agent",
"id": "spawn-retry-agent",
"description": "Retry failed task with fallback agent",
"promptTemplate": "A task failed. Recover it safely.\nTask=${TASK_SUBJECT}\nError=${ERROR}",
"agentType": "general-purpose",
"model": "sonnet"
}
]
}
]
}
我要怎么让规则只针对特定错误?
在规则内部使用 condition 块。它是可选的,但如果不写,规则会对所声明的事件和结果的任何一次出现都作出反应。
| 字段 | 作用 |
|---|---|
toolNames | 规则接受的 tool 名称列表(匹配事件里的 tool_name/toolName) |
taskStatuses | 接受的任务状态列表 |
errorIncludes | 对错误信息做不区分大小写的子串匹配 |
eventFieldEquals | 按字段路径做精确相等匹配,例如 "meta.source": "scheduler" |
"condition": {
"toolNames": ["Edit", "Write", "Bash"],
"errorIncludes": ["timeout", "permission denied"]
}
可以只通知团队,不运行任何代理吗?
可以,用 notify_team 动作代替 spawn_fallback_agent。它读取项目里配置的团队并写入消息,不需要启动新的代理:
{
"type": "notify_team",
"id": "notify-ops",
"recipients": ["*"],
"summary": "Hook chain ${RULE_ID} fired",
"messageTemplate": "Event=${EVENT_NAME} outcome=${OUTCOME}\nTask=${TASK_ID}\nError=${ERROR}"
}
如果没有配置团队文件,这个动作不会中断整条链:它会被跳过,并记录跳过的原因。这两个动作,以及第三个动作(warm_remote_capacity,提前通知运行时预热远程执行容量),接受同样的一组文本占位符:
| 占位符 | 填充内容 |
|---|---|
${EVENT_NAME} | 触发该规则的事件 |
${OUTCOME} | 事件的结果 |
${RULE_ID} | 匹配到的规则 id |
${TASK_SUBJECT} | 任务主题 |
${TASK_DESCRIPTION} | 任务描述 |
${TASK_ID} | 任务标识符 |
${ERROR} | 错误信息 |
${PAYLOAD_JSON} | 完整的事件负载,JSON 格式 |
即使不使用远程执行,保留 warm_remote_capacity 的配置也是安全的:当项目策略禁止远程会话,或没有可预热的活跃远程会话时,它会被无错误地跳过。
什么能防止恢复循环无限进行?
三重防护,并且是叠加的。maxChainDepth 会在当前链深度已经达到上限时(默认 2,上限 10)切断触发:一个引发另一个事件的 hook chain 动作,不会演变成无限链条。cooldownMs 会阻止同一条规则在配置的时间过去之前再次触发(默认 30 秒,可按规则调整)。dedupWindowMs 会在窗口期内抑制相同事件和动作组合的重复,因此同一个错误的连续爆发不会让同一个代理连续触发多次。除此之外,还有默认安全的行为:如果当前信号已经被中止,动作会被跳过,而不会尝试在一个已经不存在的状态上运行。
每次规则触发 spawn_fallback_agent,都会有一个全新的代理从零开始尝试恢复任务。在同一个错误于 cooldownMs 让规则安静下来之前重复出现几次的夜晚,这意味着连续启动多个恢复代理。在 Verboo Code 中,其中每一个都以不限量的 token 运行,所以第五次恢复尝试和第一次的成本是一样的。



