Skip to content
源码分析手册

PreToolUse:工具执行前的可编程闸门

在 Claude Code 的工具执行链中,PreToolUse 是一个极具威力的 Hook。它在模型决定调用工具之后、实际执行该工具之前触发,为开发者提供了一个可以动态审计、修改甚至拦截工具调用的最后机会。

从定义开始

PreToolUse 本质上是 工具执行的同步守卫(Synchronous Guard)

不是一个事后的日志记录器,而是一个同步阻断点。它允许你根据工具的名称、输入参数或当前上下文,实时决定是否允许这次调用。你可以把它想象成一个自定义的防火墙规则:在流量(工具请求)到达目的地(工具实现)之前,先过一遍你的安全规则。

它也不是权限询问框(那由 PermissionRequest 负责),它是一个非交互式的逻辑判定点。

实现细节

该钩子的触发逻辑深度集成在 claude-code-opensource/src/services/tools/toolExecution.ts 中,其执行逻辑如下:

  1. 触发时机:模型发出 tool_use 消息后,系统首先完成参数校验(Zod Validation)。校验通过后,立即调用 runPreToolUseHooks 派发事件。
  2. 上下文装配:系统为钩子准备了详尽的输入(PreToolUseHookInput),包括:
    • tool_name:即将被调用的工具名称。
    • tool_input:经过校验的工具参数。
    • tool_use_id:该次调用的唯一标识。
  3. 条件匹配(If Condition):利用 prepareIfConditionMatcher 对工具名或特定参数模式进行前置筛选。例如,你可以定义一个只对 Bash 工具生效,且其命令中包含特定字符串的 PreToolUse 钩子。
  4. 裁决响应(Actionable Response): 钩子可以通过返回 HookJSONOutput 实施以下干预:
    • 中断执行:通过返回 decision: "block" 并提供理由,使该次工具调用失败(模型会看到报错并尝试纠正)。
    • 参数改写(Updated Input):通过返回 updatedInput 直接改写模型即将发送给工具的参数。这是目前唯一可以“无感”修正模型错误的手段。
    • 权限覆盖:在特定逻辑下,Hook 可以强制授予或撤销该次调用的权限。

限制与陷阱

  • 同步阻塞瓶颈:由于 PreToolUse 是同步阻塞的,任何耗时的钩子操作(如网络请求或耗时的本地扫描)都会直接导致 Claude 响应变慢。
  • 非 UI 交互性:它无法在工具执行前弹出一个自定义的询问对话框。它的裁决必须是非交互式的,依赖代码逻辑而非用户操作。
  • 权限系统的优先级PreToolUse 通常运行在基础权限校验(如规则匹配)之后。即使规则允许了,PreToolUse 仍有权将其拦截。
  • 仅限主进程工具:对于某些极其底层的内建指令,系统可能会绕过完整的钩子流程,这一点在实现时需进行特定验证。

延伸阅读

源码锚点

  • claude-code-opensource/src/services/tools/toolExecution.ts — 工具执行的主循环及其对 PreToolUse 的触发。
📄 src/services/tools/toolExecution.ts — 工具执行的主循环及其对 `PreToolUse` 的触发。L130-150 of 1746
typescript
  runPreToolUseHooks,
} from './toolHooks.js'

/** Minimum total hook duration (ms) to show inline timing summary */
export const HOOK_TIMING_DISPLAY_THRESHOLD_MS = 500
/** Log a debug warning when hooks/permission-decision block for this long. Matches
 * BashTool's PROGRESS_THRESHOLD_MS — the collapsed view feels stuck past this. */
const SLOW_PHASE_LOG_THRESHOLD_MS = 2000

/**
 * Classify a tool execution error into a telemetry-safe string.
 *
 * In minified/external builds, `error.constructor.name` is mangled into
 * short identifiers like "nJT" or "Chq" — useless for diagnostics.
 * This function extracts structured, telemetry-safe information instead:
 * - TelemetrySafeError: use its telemetryMessage (already vetted)
 * - Node.js fs errors: log the error code (ENOENT, EACCES, etc.)
 * - Known error types: use their unminified name
 * - Fallback: "Error" (better than a mangled 3-char identifier)
 */
export function classifyToolError(error: unknown): string {
  • claude-code-opensource/src/services/tools/toolHooks.tsrunPreToolUseHooks 的具体实现及 Hook 决策的分发逻辑。
📄 src/services/tools/toolHooks.ts — `runPreToolUseHooks` 的具体实现及 Hook 决策的分发逻辑。L435-450 of 651
typescript
export async function* runPreToolUseHooks(
  toolUseContext: ToolUseContext,
  tool: Tool,
  processedInput: Record<string, unknown>,
  toolUseID: string,
  messageId: string,
  requestId: string | undefined,
  mcpServerType: McpServerType,
  mcpServerBaseUrl: string | undefined,
): AsyncGenerator<
  | {
      type: 'message'
      message: MessageUpdateLazy<
        AttachmentMessage | ProgressMessage<HookProgress>
      >
    }
  • claude-code-opensource/src/types/hooks.tsPreToolUseHookInput 的字段定义。
📄 src/types/hooks.ts — `PreToolUseHookInput` 的字段定义。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 开源快照的深度分析