会话收口生命周期:Stop 钩子与后台异步治理
当 Claude 完成一轮推理并停止输出时,系统并不会直接进入闲置状态,而是进入了一个名为 Stop 的生命周期收口阶段。这是运行时进行状态结算、异步背景任务调度以及下一步预测的关键时机。
从定义开始
Stop 钩子是 推理轮次结束后的治理逻辑与状态锚定点。
它不是一个简单的“生成结束回调”,而是一个复杂的异步调度引擎。每当模型停止生成(无论是正常完成、因 Token 耗尽被迫停止、还是被用户通过 Ctrl+C 中断),Stop 阶段都会被激活。它的核心价值是在用户拿回终端控制权之前,完成以下三件事:对本轮输出进行“治理”(如根据 lint 错误阻断流程)、对当前环境进行“镜像保存”(为侧边问题或重绕做准备)、以及在后台悄悄开启“推理余温”任务(如记忆提取和下步预测)。
实现细节
收口协议的核心逻辑封装在 claude-code-opensource/src/query/stopHooks.ts 的 handleStopHooks 函数中。其运行时行为可归纳为以下四个并行执行的分支:
快照与缓存对齐(Snapshotting): 为了支持
/btw和侧边问题(Side Questions),系统会在Stop阶段通过saveCacheSafeParams保存当前会话的完整上下文快照。这样做是为了确保即便后续主会话继续推进,临时开启的侧边会话也能复用此时此刻的 Prompt Cache,从而极大地节省首屏 Token 成本。治理钩子执行(Governance Hooks): 调用
executeStopHooks并行运行用户或系统定义的治理脚本。这些钩子具有极高的干预权限:- 流程阻断:如果钩子发现环境状态异常(如核心单元测试失败),它可以向主循环抛出
blockingError。 - 控制流拦截:通过
preventContinuation信号,钩子可以强行停止 Claude 的自动化执行流水线,要求用户必须介入确认。
- 流程阻断:如果钩子发现环境状态异常(如核心单元测试失败),它可以向主循环抛出
背景异步清理(Background Bookkeeping): 在非
--bare模式下,系统会以“Fire-and-forget”模式并行启动多项后台工作:- Prompt Suggestion:基于主会话的 Cache 前缀,在后台启动一个轻量级的 Fork Agent 预测用户可能的下一句输入。
- Memory Extraction:从当前对话中提取重要事实并更新本地持久化记忆层。
- Auto Dream:在后台进行深度状态整理或逻辑自洽推演。
Teammate 协议同步: 如果当前是以“队友(Teammate)”身份运行,
Stop阶段还会额外触发任务状态同步钩子,用于更新共享的 mailbox 或同步全局任务账本。
限制与陷阱
- 并行无关性:
Stop阶段的所有钩子和背景任务都是高度并行的。这意味着它们之间不应存在先后依赖关系,否则会导致不可预测的竞态冲突。 - 非阻塞背景任务:内存提取和建议预测虽然会消耗一定的资源,但它们的设计原则是“不影响用户输入”,即输入框会出现,后台任务异步执行。
- 中断也是一种 Stop:即便用户按了 Ctrl+C 强行停止输出,
handleStopHooks仍会被触发。此时系统会优先执行“紧急清理”逻辑,而非深度背景分析。 - Bare 模式的隔离:在
--bare模式下,绝大部分耗时的背景任务(如 Memory 提取和 Suggestion 生成)会被硬性跳过。
接下来看什么
- 如果你想了解提取出的记忆具体存到了哪里,请看 本地记忆层:Auto Memory 机制。
- 如果你想了解如何通过快照实现状态重绕,请看 会话重绕与检查点:Rewind 机制。
- 如果你关心后台预测是如何做到“不花冤枉钱”的,请看 Prompt Suggestions:预测与热启动机制。
源码锚点
claude-code-opensource/src/query/stopHooks.ts—handleStopHooks与executeStopHooks的主入口逻辑。
📄 src/query/stopHooks.ts — `handleStopHooks` 与 `executeStopHooks` 的主入口逻辑。
export async function* handleStopHooks(
messagesForQuery: Message[],
assistantMessages: AssistantMessage[],
systemPrompt: SystemPrompt,
userContext: { [k: string]: string },claude-code-opensource/src/services/extractMemories/extractMemories.ts— 异步记忆提取任务的实现细节。
📄 src/services/extractMemories/extractMemories.ts — 异步记忆提取任务的实现细节。
const teamMemPaths = feature('TEAMMEM')
? (require('../../memdir/teamMemPaths.js') as typeof import('../../memdir/teamMemPaths.js'))
: null
/* eslint-enable @typescript-eslint/no-require-imports */
// ============================================================================
// Helpers
// ============================================================================
/**
* Returns true if a message is visible to the model (sent in API calls).
* Excludes progress, system, and attachment messages.
*/
function isModelVisibleMessage(message: Message): boolean {
return message.type === 'user' || message.type === 'assistant'
}claude-code-opensource/src/utils/forkedAgent.ts— 用于缓存对齐的快照保存机制。
📄 src/utils/forkedAgent.ts — 用于缓存对齐的快照保存机制。
type ContentReplacementState,
cloneContentReplacementState,
} from './toolResultStorage.js'
import { createAgentId } from './uuid.js'