第三节:自定义 Hooks 开发
Hook 编写与注册

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


# 第六阶段 · 模块六 · 第三节:自定义 Hooks 开发

核心问题

如何编写自定义 Hook?如何配置和管理 Hook?如何调试 Hook?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  Hooks 系统                                                          │
        │                                                                      │
        │  自定义 Hooks 开发 ← 本节                                           │
        │  ├── 编写 Hook                                                       │
        │  ├── 配置 Hook                                                       │
        │  └── 调试 Hook                                                       │
        └─────────────────────────────────────────────────────────────────────┘
        


一、创建 Hook 文件

1.1 项目结构


        ~/.claude/
        ├── hooks/                  ← Hooks 目录
        │   ├── pre-tool.ts        ← PreToolUse Hook
        │   ├── post-tool.ts       ← PostToolUse Hook
        │   ├── permission.ts      ← PermissionRequest Hook
        │   └── session.ts         ← SessionStart Hook
        └── settings.json          ← Hooks 配置
        

1.2 基本 Hook 模板


        // hooks/my-hook.ts
        import type { HookHandler } from '../../types/hooks.js';
        
        // PreToolUse Hook 示例
        export const preToolUse: HookHandler = async (input, context) => {
          // input: HookInput
          // context: { state: AppState, ... }
        
          console.log('PreToolUse called:', input);
        
          // 业务逻辑
          if (input.toolName === 'Read') {
            // 检查文件路径
            const filePath = input.toolInput.file_path as string;
        
            if (filePath.includes('.env')) {
              return {
                continue: false,
                systemMessage: 'Reading .env files is not allowed',
              };
            }
          }
        
          // 继续执行
          return { continue: true };
        };
        


二、配置 Hooks

2.1 settings.json


        {
          "hooks": {
            "PreToolUse": ["./hooks/pre-tool.ts"],
            "PostToolUse": ["./hooks/post-tool.ts"],
            "UserPromptSubmit": ["./hooks/user-prompt.ts"],
            "SessionStart": ["./hooks/session.ts"]
          }
        }
        

2.2 多 Hook 配置


        {
          "hooks": {
            "PreToolUse": [
              "./hooks/log.ts",
              "./hooks/validate.ts",
              "./hooks/security.ts"
            ]
          }
        }
        

2.3 五问分析

问 1:Hook 路径是相对于什么的?


        // 相对于 ~/.claude/ 目录
        // ./hooks/pre-tool.ts = ~/.claude/hooks/pre-tool.ts
        
        // 或者使用绝对路径
        "/Users/me/claude-code-hooks/security.ts"
        


三、Hook 模板

3.1 PreToolUse 模板


        // hooks/templates/pre-tool.ts
        import type { HookHandler } from '../../types/hooks.js';
        
        export const preToolUse: HookHandler = async (input, context) => {
          const { toolName, toolInput } = input;
        
          // 1. 验证输入
          if (!validateInput(toolName, toolInput)) {
            return {
              continue: false,
              systemMessage: 'Invalid input',
            };
          }
        
          // 2. 安全检查
          if (isDangerous(toolName, toolInput)) {
            return {
              continue: false,
              systemMessage: 'Operation not allowed for security reasons',
            };
          }
        
          // 3. 修改输入
          const updatedInput = modifyInput(toolInput);
        
          // 4. 继续
          return {
            continue: true,
            updatedInput,
          };
        };
        
        function validateInput(toolName: string, toolInput: Record<string, unknown>): boolean {
          return true; // 实现验证逻辑
        }
        
        function isDangerous(toolName: string, toolInput: Record<string, unknown>): boolean {
          return false; // 实现安全检查
        }
        
        function modifyInput(toolInput: Record<string, unknown>): Record<string, unknown> {
          return toolInput; // 实现输入修改
        }
        

3.2 PostToolUse 模板


        // hooks/templates/post-tool.ts
        import type { HookHandler } from '../../types/hooks.js';
        
        export const postToolUse: HookHandler = async (input, context) => {
          const { toolName, toolInput, toolOutput } = input;
        
          // 1. 记录日志
          logToolExecution(toolName, toolInput, toolOutput);
        
          // 2. 后处理输出
          const processedOutput = processOutput(toolOutput);
        
          // 3. 返回结果
          return {
            continue: true,
          };
        };
        
        function logToolExecution(toolName: string, input: unknown, output: unknown): void {
          console.log(`[${toolName}]`, { input, output });
        }
        
        function processOutput(output: unknown): unknown {
          return output;
        }
        

3.3 SessionStart 模板


        // hooks/templates/session-start.ts
        import type { HookHandler } from '../../types/hooks.js';
        
        export const sessionStart: HookHandler = async (input, context) => {
          const { state } = context;
        
          // 1. 初始化资源
          await initializeResources(state);
        
          // 2. 设置环境
          await setupEnvironment(state);
        
          // 3. 添加会话上下文
          return {
            continue: true,
            additionalContext: 'Custom session context',
          };
        };
        
        async function initializeResources(state: AppState): Promise<void> {
          // 初始化数据库连接、加载缓存等
        }
        
        async function setupEnvironment(state: AppState): Promise<void> {
          // 设置环境变量、加载配置等
        }
        


四、常见 Hook 模式

4.1 日志记录


        // hooks/logging.ts
        export const preToolUse: HookHandler = async (input) => {
          console.log(`[${new Date().toISOString()}] PreToolUse:`, {
            tool: input.toolName,
            input: input.toolInput,
          });
          return { continue: true };
        };
        
        export const postToolUse: HookHandler = async (input) => {
          console.log(`[${new Date().toISOString()}] PostToolUse:`, {
            tool: input.toolName,
            output: input.toolOutput,
            duration: Date.now() - startTime,
          });
          return { continue: true };
        };
        

4.2 速率限制


        // hooks/rate-limit.ts
        const rateLimiter = new Map<string, { count: number; resetAt: number }>();
        
        export const preToolUse: HookHandler = async (input) => {
          const key = `${input.sessionId}:${input.toolName}`;
          const now = Date.now();
        
          let entry = rateLimiter.get(key);
        
          if (!entry || now > entry.resetAt) {
            entry = { count: 0, resetAt: now + 60000 }; // 1分钟窗口
            rateLimiter.set(key, entry);
          }
        
          entry.count++;
        
          if (entry.count > 10) {
            return {
              continue: false,
              systemMessage: 'Rate limit exceeded. Please wait.',
            };
          }
        
          return { continue: true };
        };
        

4.3 输入验证


        // hooks/validation.ts
        export const userPromptSubmit: HookHandler = async (input, context) => {
          const { userPrompt } = input;
        
          // 检查 prompt 长度
          if (userPrompt.length > 10000) {
            return {
              continue: false,
              systemMessage: 'Prompt too long. Maximum 10000 characters.',
            };
          }
        
          // 检查敏感词
          const sensitivePatterns = ['password', 'secret', 'api_key'];
          for (const pattern of sensitivePatterns) {
            if (userPrompt.toLowerCase().includes(pattern)) {
              console.warn(`Sensitive pattern detected: ${pattern}`);
            }
          }
        
          return { continue: true };
        };
        


五、调试 Hooks

5.1 添加日志


        // hooks/debug.ts
        export const preToolUse: HookHandler = async (input, context) => {
          // 详细日志
          console.log('=== PreToolUse Debug ===');
          console.log('toolName:', input.toolName);
          console.log('toolInput:', JSON.stringify(input.toolInput, null, 2));
          console.log('sessionId:', input.sessionId);
          console.log('context.state:', Object.keys(context.state));
        
          try {
            const result = await processTool(input, context);
            console.log('result:', JSON.stringify(result, null, 2));
            return result;
          } catch (error) {
            console.error('=== Error ===');
            console.error(error);
            throw error;
          }
        };
        

5.2 使用 try-catch


        export const preToolUse: HookHandler = async (input, context) => {
          try {
            // 业务逻辑
            const result = await riskyOperation(input);
        
            return { continue: true, ...result };
          } catch (error) {
            // 记录错误
            console.error('Hook error:', error);
        
            // 返回错误信息
            return {
              continue: false,
              systemMessage: `Hook error: ${error.message}`,
            };
          }
        };
        

5.3 逐步执行


        // 分步骤执行,便于定位问题
        export const preToolUse: HookHandler = async (input, context) => {
          // Step 1: 验证
          const validation = validate(input);
          if (!validation.valid) {
            return { continue: false, systemMessage: validation.message };
          }
        
          // Step 2: 转换
          const transformed = transform(input);
          if (!transformed.success) {
            return { continue: false, systemMessage: transformed.message };
          }
        
          // Step 3: 执行
          return { continue: true, updatedInput: transformed.data };
        };
        


六、思考题

思考题 1:Hook 如何处理异步操作?

答案


        // Hook 支持 async/await
        export const postToolUse: HookHandler = async (input, context) => {
          // 异步操作
          const result = await fetch('https://api.example.com/log', {
            method: 'POST',
            body: JSON.stringify({ tool: input.toolName }),
          });
        
          return { continue: true };
        };
        


思考题 2:多个 Hook 冲突怎么办?

答案


        // 配置顺序决定执行顺序
        // 后执行的 Hook 覆盖先执行的
        
        // 如果 hook1 返回 { continue: false }
        // hook2 不会执行(因为流程已中断)
        
        // 解决方案:使用 continue: true 让流程继续
        // 在各自的 Hook 中独立处理
        


思考题 3:如何让 Hook 只在特定条件下执行?

答案


        // 在 Hook 内部检查条件
        export const preToolUse: HookHandler = async (input, context) => {
          // 只在特定工具名时处理
          if (input.toolName !== 'Bash') {
            return { continue: true };  // 跳过
          }
        
          // 只在特定会话时处理
          if (!input.sessionId.startsWith('project-')) {
            return { continue: true };  // 跳过
          }
        
          // 业务逻辑
          return processBash(input);
        };
        


七、延伸阅读

资源说明
Claude Code Hooks 文档https://docs.anthropic.com/claude-code/hooks
Hooks SDK 示例内置示例在 `src/hooks/`


八、下节预告

下一节我们将深入 Hooks 与权限系统

- Hook 如何影响权限决策

- 权限与 Hook 的交互流程

- 安全的 Hook 实践


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

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

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