Skip to content
源码分析手册

PostToolUse:执行后的结果审查与自动回填

PostToolUse 钩子位于工具执行链的末端。它在工具执行完成并生成结果后、模型看到该结果之前触发,主要用于对结果进行审计、修正或补充元数据。

一句话讲清楚

PostToolUse 本质上是 工具产出的过滤器与增强器(Result Filter & Augmentor)

不是一个撤销工具操作的钩子(因为执行已经发生了),而是解决“模型看到的不是真相”或“真相不完整”的问题。无论工具是成功(Success)还是失败(Failure),该钩子都有机会介入。你可以利用它来验证文件写入是否符合风格规范、自动捕获特定的报错并给出专家级建议,或者在工具产出中附加额外的调试信息。

从源码看实现

其运行时行为主要体现在 claude-code-opensource/src/services/tools/toolExecution.ts 的后处理逻辑中:

  1. 分流处理: 系统会根据执行结果分别派发 PostToolUse(成功时)或 PostToolUseFailure(失败时)事件。
  2. 丰富的上下文(Hook Input): 输入信息包括:
    • tool_nametool_input:识别该结果对应的操作。
    • tool_result:成功时,钩子可以看到原始的文本、图片或附件产出。
    • error:失败时,钩子可以看到具体的错误码和堆栈。
  3. 结果改写与上下文注入: 钩子通过 HookJSONOutput 注入 additionalContexts
    • 补充信息:例如,在 Bash 命令执行完后,钩子自动运行 ls 并将受影响的目录树作为附件回填,而无需模型再次调用。
    • 错误重塑:捕获到一个晦涩的编译错误后,钩子查阅本地文档库,并将解决方案作为“系统备注”直接喂给模型。
  4. 会话流干预: 如果钩子判定结果中包含严重的安全隐患或不符合业务逻辑,它可以抛出错误来覆盖工具的原始产出,从而强制引导模型进入纠错路径。

别踩这些坑

  • 物理操作已成定局PostToolUse 触发时,写文件等物理操作已经完成了。如果需要阻止操作,务必使用 PreToolUse
  • 不阻塞 UI 渲染:工具产出通常已经渲染在终端上,钩子的主要作用是影响模型的“可见视图(Model View)”。
  • 增量开销敏感:由于需要处理工具结果(可能是巨大的文本块),该钩子对内存和 CPU 更加敏感,应避免在其中进行大规模的数据解析。
  • 重试逻辑的干扰:如果钩子改写了报错信息,可能会干扰模型内建的自动重试(Auto-retry)逻辑。

继续探索

源码锚点

  • claude-code-opensource/src/services/tools/toolExecution.ts — 后置钩子的派发逻辑。
📄 src/services/tools/toolExecution.ts — 后置钩子的派发逻辑。L8-11 of 1746
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.tsrunPostToolUseHooks 的具体实现及 Hook 决策的分发逻辑。
📄 src/services/tools/toolHooks.ts — `runPostToolUseHooks` 的具体实现及 Hook 决策的分发逻辑。L39-68 of 651
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.tsPostToolUseHookInput 结构定义。
📄 src/types/hooks.ts — `PostToolUseHookInput` 结构定义。L22-24 of 291
typescript
export function isHookEvent(value: string): value is HookEvent {
  return HOOK_EVENTS.includes(value as HookEvent)
}

基于 Claude Code v2.1.88 开源快照的深度分析