PostToolUse:执行后的结果审查与自动回填
PostToolUse 钩子位于工具执行链的末端。它在工具执行完成并生成结果后、模型看到该结果之前触发,主要用于对结果进行审计、修正或补充元数据。
一句话讲清楚
PostToolUse 本质上是 工具产出的过滤器与增强器(Result Filter & Augmentor)。
它不是一个撤销工具操作的钩子(因为执行已经发生了),而是解决“模型看到的不是真相”或“真相不完整”的问题。无论工具是成功(Success)还是失败(Failure),该钩子都有机会介入。你可以利用它来验证文件写入是否符合风格规范、自动捕获特定的报错并给出专家级建议,或者在工具产出中附加额外的调试信息。
从源码看实现
其运行时行为主要体现在 claude-code-opensource/src/services/tools/toolExecution.ts 的后处理逻辑中:
- 分流处理: 系统会根据执行结果分别派发
PostToolUse(成功时)或PostToolUseFailure(失败时)事件。 - 丰富的上下文(Hook Input): 输入信息包括:
tool_name与tool_input:识别该结果对应的操作。tool_result:成功时,钩子可以看到原始的文本、图片或附件产出。error:失败时,钩子可以看到具体的错误码和堆栈。
- 结果改写与上下文注入: 钩子通过
HookJSONOutput注入additionalContexts:- 补充信息:例如,在 Bash 命令执行完后,钩子自动运行
ls并将受影响的目录树作为附件回填,而无需模型再次调用。 - 错误重塑:捕获到一个晦涩的编译错误后,钩子查阅本地文档库,并将解决方案作为“系统备注”直接喂给模型。
- 补充信息:例如,在 Bash 命令执行完后,钩子自动运行
- 会话流干预: 如果钩子判定结果中包含严重的安全隐患或不符合业务逻辑,它可以抛出错误来覆盖工具的原始产出,从而强制引导模型进入纠错路径。
别踩这些坑
- 物理操作已成定局:
PostToolUse触发时,写文件等物理操作已经完成了。如果需要阻止操作,务必使用PreToolUse。 - 不阻塞 UI 渲染:工具产出通常已经渲染在终端上,钩子的主要作用是影响模型的“可见视图(Model View)”。
- 增量开销敏感:由于需要处理工具结果(可能是巨大的文本块),该钩子对内存和 CPU 更加敏感,应避免在其中进行大规模的数据解析。
- 重试逻辑的干扰:如果钩子改写了报错信息,可能会干扰模型内建的自动重试(Auto-retry)逻辑。
继续探索
- 如果你想在操作前拦截它,请看 PreToolUse:工具执行前的闸门。
- 如果你想了解这些补回的上下文存到了哪里,请看 会话钩子:SessionStart 与 SessionStop。
- 如果你对 Hook 的并行执行机制感兴趣,请看 Hook 系统架构解析。
源码锚点
claude-code-opensource/src/services/tools/toolExecution.ts— 后置钩子的派发逻辑。
📄 src/services/tools/toolExecution.ts — 后置钩子的派发逻辑。
typescript
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from 'src/services/analytics/index.js'
import {claude-code-opensource/src/services/tools/toolHooks.ts—runPostToolUseHooks的具体实现及 Hook 决策的分发逻辑。
📄 src/services/tools/toolHooks.ts — `runPostToolUseHooks` 的具体实现及 Hook 决策的分发逻辑。
typescript
export async function* runPostToolUseHooks<Input extends AnyObject, Output>(
toolUseContext: ToolUseContext,
tool: Tool<Input, Output>,
toolUseID: string,
messageId: string,
toolInput: Record<string, unknown>,
toolResponse: Output,
requestId: string | undefined,
mcpServerType: McpServerType,
mcpServerBaseUrl: string | undefined,
): AsyncGenerator<PostToolUseHooksResult<Output>> {
const postToolStartTime = Date.now()
try {
const appState = toolUseContext.getAppState()
const permissionMode = appState.toolPermissionContext.mode
let toolOutput = toolResponse
for await (const result of executePostToolHooks(
tool.name,
toolUseID,
toolInput,
toolOutput,
toolUseContext,
permissionMode,
toolUseContext.abortController.signal,
)) {
try {
// Check if we were aborted during hook execution
// IMPORTANT: We emit a cancelled event per hook
if (claude-code-opensource/src/types/hooks.ts—PostToolUseHookInput结构定义。
📄 src/types/hooks.ts — `PostToolUseHookInput` 结构定义。
typescript
export function isHookEvent(value: string): value is HookEvent {
return HOOK_EVENTS.includes(value as HookEvent)
}