StopHooks续跑判定解析
StopHooks续跑判定解析
1. 这份文档回答什么问题
这份文档解释:
为什么在 query.ts 里,即便模型已经没有再发 tool_use,系统仍然可能不结束,而是继续下一轮。
答案就在:
src/query/stopHooks.ts
2. 核心定位
handleStopHooks(...) 不是普通的“收尾回调”。
它在整个 query loop 里的位置更接近:
模型想停下时的最终裁决层。
也就是说:
- 模型说“我答完了”
- 不代表系统立刻认可
- stop hooks 还有权把这次“完成”改判成“继续”
3. 它的输入是什么
handleStopHooks(...) 会拿到:
messagesForQueryassistantMessagessystemPromptuserContextsystemContexttoolUseContextquerySource
也就是说,它看到的是:
本轮结束时几乎完整的对话上下文。
因此它不是一个孤立 hook,而是有足够信息做“是否允许停止”的判断。
4. 它在 query.ts 中的作用
query.ts 在确认:
- 本轮没有新的
tool_use - 没有先被错误恢复逻辑接管
之后,才会进入 stop hook 判定。
这说明 stop hook 的角色是:
在“看起来可以结束”的时刻做最后一轮业务级审查。
5. handleStopHooks 先做什么
函数一开始先构造:
stopHookContext
它里面包含完整上下文,并会在主线程 query 时调用:
saveCacheSafeParams(...)
这说明 stop hooks 不只是为了“执行外部脚本”,它还承担了:
- 为后续
/btw - side question
- prompt suggestion
等机制保存上下文快照的职责。
6. 它不只运行 Stop hook
这是一个很容易忽略的点。
handleStopHooks(...) 名字叫 stop hook,但实际内部还会顺带处理:
- Template job classification
- Prompt suggestion
- Extract memories
- Auto dream
- Chicago MCP turn-end cleanup
所以它不只是一个狭义 hook 函数,而是:
“回合结束阶段的后台治理入口”。
7. 真正的 Stop hook 判定
真正核心的是:
executeStopHooks(...)
handleStopHooks(...) 会异步消费这个 generator,并持续接收:
messageblockingErrorpreventContinuation
这三类输出分别代表:
7.1 普通进度消息
说明 hook 正在跑,或者跑出了非阻塞输出。
7.2 blockingError
说明 hook 判定本轮不能就这样结束。
这时 handleStopHooks(...) 会把它包装成:
createUserMessage({ isMeta: true, ... })
再回注给 query loop。
所以阻塞错误不是只显示给用户看,它还会真的变成下一轮输入的一部分。
7.3 preventContinuation
这表示 hook 直接要求:
- 不要再继续自动推进
这是比 blockingError 更强的裁决。
8. 为什么说它能“把完成改判成继续”
因为 query.ts 收到 stop hook 结果后会分三种处理:
8.1 preventContinuation
直接结束,不再继续。
8.2 blockingErrors.length > 0
把这些阻塞消息加入 messages,重建新的 State,然后 continue。
这就是“模型本来想结束,但系统又追加一轮”的来源。
8.3 都没有
才真正允许结束。
所以 stop hooks 的地位不是“日志插件”,而是:
直接参与 query loop 控制流。
9. summary message 和 attachment 的作用
当 hooks 跑过后,系统还会生成:
createStopHookSummaryMessage(...)
如果 hook 要阻止继续,还会补一条:
hook_stopped_continuationattachment
这说明 hook 的结果不仅影响控制流,还会以结构化形式记录进消息链和 UI 可见层。
10. teammate 相关 hooks
如果当前是 teammate 场景,handleStopHooks(...) 还会继续处理:
TaskCompletedTeammateIdle
它们遵循和 Stop hook 类似的语义:
- 可能给出 blocking error
- 可能要求 prevent continuation
这进一步说明:
Claude Code 的“结束判定”不是单一来源,而是多类 hook 共同裁决。
11. 出错和中断时的行为
如果 stop hook 自己执行出错:
- 不会把整个 query 判成失败
- 而是生成一条 warning system message
如果 stop hook 执行期间用户中断:
- 会立刻返回
preventContinuation: true
这说明 hook 层虽然强,但仍然不能凌驾于用户主动中断之上。
12. 一句话总结
stopHooks.ts 的本质不是“模型停下之后顺手跑个钩子”,而是:
Claude Code 在一轮对话结束前的最终裁决层,它可以把“看起来已经完成”的 assistant 回复改判为“必须继续一轮”,因此是 query loop 状态机的一部分。