职责概述

工具执行是 Claude Code 的核心能力通道。当模型返回 tool_use block 后,系统需要完成工具匹配、参数校验、权限检查、Hook 前置/后置处理、并发控制、结果处理等一系列步骤。本页完整追踪这一端到端流程,涵盖正常路径、错误路径、重试逻辑和并发安全机制。

架构设计

工具执行涉及五个层级,自上而下协作完成一次调用:

API 响应层
query.ts — 主循环
QueryEngine.ts — 引擎
调度层
tools.ts — 工具注册表
Tool.ts — 工具匹配
findToolByName()
安全层
permissions.ts — 权限判定
canUseTool() — 交互确认
PermissionRequest Hook
Hook 层
PreToolUse / PostToolUse
execHttpHook / execAgentHook
execPromptHook
执行层
tool.call() — 实际调用
result → API 返回

核心数据流

以下是工具执行的完整流程,包含决策点和错误路径:

Step 1: API 响应解析
模型返回 stop_reason='tool_use',响应中包含一个或多个 tool_use ContentBlock。query.ts 主循环提取所有 tool_use blocks。
Step 2: 工具匹配
调用 findToolByName(tools, name) 在注册表中查找。支持别名匹配(tool.aliases),未找到则返回错误消息。
Step 3: 并发安全检查
调用 tool.isConcurrencySafe(input)。并发不安全的工具(如 FileEdit)在 plan 模式下串行执行;安全的工具(如 Read、Glob)可并行执行。
Step 4: 权限检查
canUseTool(tool, input, context) 检查 ToolPermissionContext。结果为 allow → 继续;deny → 返回错误;ask → 弹出用户确认对话框。
Step 5: PreToolUse Hook
执行所有匹配的 PreToolUse Hook。支持 4 种 Hook 类型:command(子进程)、http(HTTP POST)、prompt(LLM 单次调用)、agent(多轮 LLM)。exit_code=2 阻止执行。
Step 6: 工具执行
调用 tool.call(args, context, canUseTool, parentMessage, onProgress)。执行过程中通过 ToolCallProgress 回调报告进度。支持 AbortSignal 取消。
Step 7: 结果处理
结果经过 maxResultSizeChars 限制检查。超大结果持久化到磁盘(diskOutput),仅向模型返回摘要预览。正常结果直接作为 tool_result 返回。
Step 8: PostToolUse Hook
执行 PostToolUse / PostToolUseFailure Hook。exit_code=2 将 stderr 注入模型上下文。失败时触发 PostToolUseFailure Hook,携带 error_type 信息。
Step 9: 返回 API
所有 tool_result 组装为 UserMessage,发送给 API 开始下一轮对话。主循环继续直到模型返回 stop_reason='end_turn'。

错误与重试路径

权限被拒 → PermissionDenied Hook
当自动模式分类器拒绝工具调用时,触发 PermissionDenied Hook。Hook 可返回 {"retry": true} 让模型重试。
PreToolUse 阻止 → 返回错误给模型
Hook exit_code=2 时,stderr 内容作为错误消息返回给模型,模型可调整策略。
执行超时 → AbortSignal 取消
每个工具调用绑定 AbortSignal。用户按 Escape 或 hook 超时均触发取消,返回 interrupted 错误。
API 错误 → StopFailure Hook
rate_limit / authentication_failed / server_error 等错误触发 StopFailure Hook。该 Hook 为 fire-and-forget,输出和退出码被忽略。

关键类型与接口

Tool 核心接口

// Tool.ts:362 — 工具类型定义
export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  readonly name: string
  aliases?: string[]
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>,
  ): Promise<ToolResult<Output>>
  isConcurrencySafe(input: z.infer<Input>): boolean
  isReadOnly(input: z.infer<Input>): boolean
  isDestructive?(input: z.infer<Input>): boolean
  maxResultSizeChars: number
  interruptBehavior?(): 'cancel' | 'block'
}

工具匹配函数

// Tool.ts:348-360 — 工具查找
export function toolMatchesName(
  tool: Tool, name: string,
): boolean {
  return tool.name === name ||
    tool.aliases?.includes(name) === true
}

export function findToolByName(
  tools: Tools, name: string,
): Tool | undefined {
  return tools.find(t => toolMatchesName(t, name))
}

ToolUseContext 执行上下文

// Tool.ts:158-300 — 工具执行上下文
export type ToolUseContext = {
  options: {
    tools: Tools
    mainLoopModel: string
    isNonInteractiveSession: boolean
    thinkingConfig: ThinkingConfig
    mcpClients: Map<string, MCPServerConnection>
    // ...更多选项
  }
  agentId?: string
  abortController: AbortController
  setAppState: (updater: (prev: AppState) => AppState) => void
  getAppState: () => AppState
  readFileState: FileStateCache
  contentReplacementState?: ContentReplacementState
  setResponseLength: (updater: (prev: number) => number) => void
  // ...更多字段
}

Hook 结果类型

// hooks.ts — Hook 执行结果
export type HookResult = {
  hook: HookCommand
  outcome: 'success' | 'blocking' | 'cancelled'
      | 'non_blocking_error'
  message?: AttachmentMessage
  blockingError?: { blockingError: string; command: string }
  preventContinuation?: boolean
  stopReason?: string
}

Hook 事件元数据(部分)

// hookEvents.ts:27-99 — Hook 事件定义
PreToolUse: {
  summary: 'Before tool execution',
  matcherMetadata: {
    fieldToMatch: 'tool_name',
    values: toolNames,  // 动态填充所有注册工具名
  },
},
PostToolUse: {
  summary: 'After tool execution',
  matcherMetadata: { fieldToMatch: 'tool_name', values: toolNames },
},
PostToolUseFailure: {
  summary: 'After tool execution fails',
  matcherMetadata: { fieldToMatch: 'tool_name', values: toolNames },
},
PermissionDenied: {
  summary: 'After auto mode classifier denies a tool call',
  // 可返回 {"retry": true} 让模型重试
},
PermissionRequest: {
  summary: 'When a permission dialog is displayed',
  // 可返回 decision: allow/deny 覆盖用户选择
}

设计模式与亮点

1. 四种 Hook 执行策略

Hook 系统支持四种执行模式,每种适用于不同场景:

  • command — 子进程执行,支持超时和流式输出
  • http — HTTP POST,支持 SSRF 防护、环境变量插值、sandbox 代理
  • prompt — 单轮 LLM 调用(Haiku 模型),返回结构化 JSON
  • agent — 多轮 LLM 查询,支持工具调用,最多 50 轮
// 四种 Hook 类型
type HookCommand =
  | { type: 'command'; command: string; timeout?: number }
  | { type: 'http'; url: string; headers?: Record<string, string> }
  | { type: 'prompt'; prompt: string; model?: string }
  | { type: 'agent'; prompt: string; model?: string; timeout?: number }

2. 异步 Hook 注册表

AsyncHookRegistry 管理长时间运行的 Hook 进程。Hook 可返回 {"async": true} 进入异步模式,系统每 500ms 轮询 stdout 检查 JSON 响应。最多缓存 100 个待处理事件。

3. 结果持久化策略

工具输出超过 maxResultSizeChars 时自动持久化到磁盘,模型仅收到预览摘要。Read 工具设为 Infinity 避免循环(Read 文件 → 持久化 → 又要 Read 持久化文件)。

4. 安全分层防护

HTTP Hook 执行经过多层安全检查:

  • URL 白名单(allowedHttpHookUrls 通配符匹配)
  • 环境变量白名单(防止密钥泄露)
  • SSRF 防护(ssrfGuardedLookup 屏蔽私有 IP)
  • Header 注入防护(剥离 CR/LF/NUL 字节)
  • Sandbox 代理强制域名白名单

5. 工具并发控制

系统根据 isConcurrencySafe() 将工具分为两类:

  • 安全 — Read, Glob, Grep, WebSearch 可并行
  • 不安全 — FileEdit, FileWrite, Bash 串行执行

这防止了文件写入冲突,同时最大化只读操作的吞吐量。

6. 工具延迟加载

通过 shouldDefer 标记,工具 schema 可延迟发送。模型需要先调用 ToolSearch 工具获取完整 schema,减少首次 prompt 的 token 消耗。alwaysLoad 标记确保关键工具始终可见。

开发者实践指南

新增工具只需实现 Tool 接口,系统自动处理匹配、权限、Hook 和并发。

如何新增一个工具

  1. tools/ 下创建目录,如 tools/MyTool/MyTool.ts
  2. 使用 buildTool() 工厂函数定义工具,自动填充默认实现
  3. 实现 call()description()inputSchema 三个必填字段
  4. tools.ts 中注册到工具列表
  5. 如需自定义权限,实现 isReadOnly()isConcurrencySafe()
// buildTool 工厂函数 — 自动填充默认值
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return { ...TOOL_DEFAULTS, ...def }
}

// 默认值
const TOOL_DEFAULTS = {
  isConcurrencySafe: () => false,   // 默认不安全
  isReadOnly: () => false,          // 默认非只读
  isEnabled: () => true,            // 默认启用
  maxResultSizeChars: 50000,         // 50K 字符上限
}

Hook 调试技巧

架构师决策指南

设计权衡

Hook 同步 vs 异步:PreToolUse Hook 默认同步执行,会阻塞工具调用。对于耗时操作(如网络请求),可返回 {"async": true} 异步处理,但需要轮询机制增加复杂度。

结果截断策略maxResultSizeChars 默认 50K,平衡了上下文窗口利用率和信息完整性。Read 设为 Infinity 是因为其自身已有行数限制,持久化会导致循环读取。

权限模型选择PermissionRequest Hook 允许覆盖用户选择,适合企业场景的自动化审批,但也带来安全风险。需要配合 allowedHttpHookUrls 白名单使用。

扩展性考量

  • 工具注册表是静态数组,运行时不可动态添加(除了 MCP 工具)
  • Hook 按 matcher 分组查找,sortMatchersByPriority 确保高优先级 Hook 先执行
  • 并发工具数无硬性上限,受 API 并行 tool_use 数量限制
  • Hook 执行超时独立于工具执行超时,两者通过 createCombinedAbortSignal 组合

性能关键路径

  • 工具匹配:O(n) 线性扫描,n 为工具数量(~40),可接受
  • Hook 查找:memoized 按 toolNames 排序拼接缓存,避免重复计算
  • 结果持久化:异步写入,不阻塞主循环

代码索引

文件行数说明
Tool.ts~793Tool 接口定义、findToolByName、buildTool 工厂
tools.ts~200工具注册表、ALL_AGENT_DISALLOWED_TOOLS
query.ts~800主查询循环、tool_use 分发
QueryEngine.ts~300查询引擎、compact、contextCollapse
utils/hooks/hookEvents.ts~400Hook 事件元数据、分组查找
utils/hooks/execHttpHook.ts~243HTTP Hook 执行、SSRF 防护
utils/hooks/execAgentHook.ts~340Agent Hook 多轮执行
utils/hooks/execPromptHook.ts~212Prompt Hook 单轮 LLM 调用
utils/hooks/AsyncHookRegistry.ts~310异步 Hook 注册表、轮询检查
utils/hooks/hooksSettings.ts~200Hook 配置解析、优先级排序
utils/permissions/permissions.ts~300权限判定、hasPermissionsToUseTool
utils/toolResultStorage.ts~150工具结果持久化、预览生成
utils/task/diskOutput.ts~100磁盘输出管理、evictTaskOutput