第二节:内置 Hooks 详解
常用 Hook 与用法

作者:小学子 📚 | 日期:2026年4月2日 | 第六阶段 · 模块六


# 第六阶段 · 模块六 · 第二节:内置 Hooks 详解

核心问题

每个内置 Hook 的输入输出是什么?PreToolUse 如何拦截工具调用?PermissionRequest 的工作流程是什么?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  Hooks 系统                                                          │
        │                                                                      │
        │  内置 Hooks ← 本节                                                 │
        │  ├── PreToolUse ──> 工具执行前                                     │
        │  ├── PostToolUse ──> 工具执行后                                    │
        │  └── PermissionRequest ──> 权限请求                                │
        └─────────────────────────────────────────────────────────────────────┘
        


一、PreToolUse

1.1 触发时机


        用户/AI → 工具调用 → PreToolUse → 工具执行
        

1.2 输入结构

源码位置:`src/types/hooks.ts` 第 73 行


        type PreToolUseInput = {
          hookEventName: 'PreToolUse';
          toolName: string;           // 'Read', 'Write', 'Grep'...
          toolInput: Record<string, unknown>;  // 工具输入参数
          sessionId: string;
          userIdentity: {
            email?: string;
            name?: string;
          } | null;
        };
        

1.3 返回结构


        type PreToolUseResult = {
          continue?: boolean;           // 是否继续执行,默认 true
          updatedInput?: object;       // 修改后的输入
          additionalContext?: string;    // 额外上下文
          permissionDecision?: {
            behavior: 'allow' | 'deny' | 'pause';
            reason?: string;
          };
        };
        

1.4 使用示例


        // hooks/pre-tool.ts
        export async function preToolUse(input: PreToolUseInput) {
          // 检查文件路径
          if (input.toolName === 'Read') {
            const filePath = input.toolInput.file_path;
        
            if (filePath.includes('.env')) {
              return {
                continue: false,
                systemMessage: 'Cannot read .env files for security reasons',
              };
            }
          }
        
          // 允许执行
          return { continue: true };
        }
        

1.5 五问分析

问 1:如何修改工具输入?


        // 通过 updatedInput
        return {
          continue: true,
          updatedInput: {
            ...input.toolInput,
            // 添加默认参数
            encoding: 'utf-8',
            // 或者修改现有参数
            maxLines: 1000,
          },
        };
        

问 2:permissionDecision 和 continue 的区别?


        // continue: false → 直接阻止,不记录
        // permissionDecision: { behavior: 'deny' } → 记录权限拒绝
        //   → 触发 PermissionDenied Hook
        //   → 可能触发权限请求提示
        


二、PostToolUse

2.1 触发时机


        工具执行 → PostToolUse → 返回结果给 AI
        

2.2 输入结构

源码位置:`src/types/hooks.ts` 第 101 行


        type PostToolUseInput = {
          hookEventName: 'PostToolUse';
          toolName: string;
          toolInput: Record<string, unknown>;
          toolOutput: unknown;         // 工具返回结果
          wasRateLimited?: boolean;
          invocationId: string;
          additionalContext?: string;
        };
        

2.3 返回结构


        type PostToolUseResult = {
          continue?: boolean;
          updatedMCPToolOutput?: unknown;  // 只对 MCP 工具有效
          additionalContext?: string;
        };
        

2.4 使用示例


        // hooks/post-tool.ts
        export async function postToolUse(input: PostToolUseInput) {
          // 日志记录
          console.log(`Tool ${input.toolName} executed with output:`, input.toolOutput);
        
          // 修改 MCP 工具输出
          if (input.toolName === 'mcp__custom') {
            return {
              continue: true,
              updatedMCPToolOutput: {
                ...input.toolOutput,
                modified: true,
              },
            };
          }
        
          return { continue: true };
        }
        

2.5 五问分析

问 1:updatedMCPToolOutput 有什么用?


        // 只对 MCP 工具生效
        // 可以修改 MCP 工具的返回结果
        
        return {
          continue: true,
          updatedMCPToolOutput: {
            // 新的输出,会替换原输出
            data: 'modified data',
          },
        };
        

问 2:wasRateLimited 是什么?


        // 当工具调用被限流时为 true
        // 可能是 API 速率限制
        // 或 MCP 服务器限制
        
        if (input.wasRateLimited) {
          console.warn('Tool was rate limited');
        }
        


三、PostToolUseFailure

3.1 输入结构

源码位置:`src/types/hooks.ts` 第 109 行


        type PostToolUseFailureInput = {
          hookEventName: 'PostToolUseFailure';
          toolName: string;
          toolInput: Record<string, unknown>;
          error: string;               // 错误消息
          errorCode?: string;
          invocationId: string;
          additionalContext?: string;
        };
        

3.2 使用示例


        // hooks/tool-failure.ts
        export async function postToolUseFailure(input: PostToolUseFailureInput) {
          // 记录错误
          console.error(`Tool ${input.toolName} failed:`, input.error);
        
          // 发送告警
          if (input.toolName === 'Write') {
            await sendAlert({
              type: 'write_failure',
              error: input.error,
            });
          }
        
          return { continue: true };
        }
        


四、PermissionRequest

4.1 触发时机


        工具调用 → 需要权限 → PermissionRequest Hook
        

4.2 输入结构

源码位置:`src/types/hooks.ts` 第 121 行


        type PermissionRequestInput = {
          hookEventName: 'PermissionRequest';
          toolName: string;
          toolInput: Record<string, unknown>;
          permission: {
            type: string;              // 'Read' | 'Write' | 'Browser'...
            reason: string;           // 为什么需要这个权限
          };
          prompt?: string;            // 用户看到的提示
          invocationId: string;
        };
        

4.3 返回结构


        type PermissionRequestResult = {
          decision: {
            behavior: 'allow' | 'deny' | 'pause';
            updatedInput?: object;
            updatedPermissions?: PermissionUpdate[];
            message?: string;
            interrupt?: boolean;
          };
        };
        

4.4 使用示例


        // hooks/permission.ts
        export async function permissionRequest(input: PermissionRequestInput) {
          // 自动批准安全操作
          if (input.toolName === 'Read') {
            const path = input.toolInput.file_path;
        
            // 只允许读取 src 目录
            if (path.startsWith('/project/src')) {
              return {
                decision: {
                  behavior: 'allow',
                  message: 'Auto-approved: reading from src directory',
                },
              };
            }
          }
        
          // 阻止危险操作
          if (input.toolName === 'Bash') {
            const cmd = input.toolInput.command;
        
            // 阻止删除操作
            if (cmd.includes('rm -rf')) {
              return {
                decision: {
                  behavior: 'deny',
                  message: 'Cannot execute rm -rf commands',
                  interrupt: true,
                },
              };
            }
          }
        
          // 其他操作需要用户确认
          return {
            decision: {
              behavior: 'pause',  // 暂停,等待用户确认
            },
          };
        }
        

4.5 五问分析

问 1:allow 和 pause 的区别?


        // allow → 自动批准,不询问用户
        // pause → 暂停,显示提示给用户,等待确认
        // deny → 拒绝执行
        
        // interrupt: true → 即使是 pause 也中断流程
        


五、UserPromptSubmit

5.1 触发时机


        用户输入 → UserPromptSubmit → 发送给 AI
        

5.2 输入结构


        type UserPromptSubmitInput = {
          hookEventName: 'UserPromptSubmit';
          userPrompt: string;
          promptElicitation?: {
            id: string;
            message: string;
            options: Array<{
              key: string;
              label: string;
            }>;
          };
        };
        

5.3 返回结构


        type UserPromptSubmitResult = {
          continue?: boolean;
          userPrompt?: string;         // 修改后的 prompt
          additionalContext?: string;
        };
        


六、思考题

思考题 1:如何实现白名单机制?

答案


        // 在 PreToolUse 中实现白名单
        const ALLOWED_PATHS = ['/project/src', '/project/docs'];
        
        export async function preToolUse(input: PreToolUseInput) {
          if (input.toolName === 'Read') {
            const path = input.toolInput.file_path;
        
            if (!ALLOWED_PATHS.some(p => path.startsWith(p))) {
              return {
                continue: false,
                systemMessage: 'Path not in whitelist',
              };
            }
          }
          return { continue: true };
        }
        


思考题 2:Hook 如何处理敏感数据?

答案


        // 在 PostToolUse 中过滤敏感信息
        export async function postToolUse(input: PostToolUseInput) {
          let output = input.toolOutput;
        
          if (typeof output === 'string') {
            // 过滤 API 密钥
            output = output.replace(/sk-[a-zA-Z0-9]{20,}/g, '***REDACTED***');
          }
        
          return { continue: true };
        }
        


思考题 3:如何调试 Hook?

答案


        // 添加日志
        export async function preToolUse(input: PreToolUseInput) {
          console.log('PreToolUse called:', JSON.stringify(input, null, 2));
        
          try {
            // 业务逻辑
            const result = await process(input);
        
            console.log('PreToolUse result:', result);
            return result;
          } catch (error) {
            console.error('PreToolUse error:', error);
            throw error;  // 重新抛出以查看完整堆栈
          }
        }
        


七、延伸阅读

文件核心内容
`src/types/hooks.ts`Hook 类型完整定义
`src/utils/hooks.ts`Hook 执行引擎


八、下节预告

下一节我们将深入 自定义 Hooks 开发

- 编写自定义 Hook

- Hook 配置管理

- Hook 调试技巧


*- 第一轮:□ 事实准确性*

*- 第二轮:□ 深度与洞见*

*- 第三轮:□ 可读性与价值*