# 第六阶段 · 模块六 · 第三节:自定义 Hooks 开发
如何编写自定义 Hook?如何配置和管理 Hook?如何调试 Hook?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ Hooks 系统 │
│ │
│ 自定义 Hooks 开发 ← 本节 │
│ ├── 编写 Hook │
│ ├── 配置 Hook │
│ └── 调试 Hook │
└─────────────────────────────────────────────────────────────────────┘
~/.claude/
├── hooks/ ← Hooks 目录
│ ├── pre-tool.ts ← PreToolUse Hook
│ ├── post-tool.ts ← PostToolUse Hook
│ ├── permission.ts ← PermissionRequest Hook
│ └── session.ts ← SessionStart Hook
└── settings.json ← Hooks 配置
// 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": {
"PreToolUse": ["./hooks/pre-tool.ts"],
"PostToolUse": ["./hooks/post-tool.ts"],
"UserPromptSubmit": ["./hooks/user-prompt.ts"],
"SessionStart": ["./hooks/session.ts"]
}
}
{
"hooks": {
"PreToolUse": [
"./hooks/log.ts",
"./hooks/validate.ts",
"./hooks/security.ts"
]
}
}
问 1:Hook 路径是相对于什么的?
// 相对于 ~/.claude/ 目录
// ./hooks/pre-tool.ts = ~/.claude/hooks/pre-tool.ts
// 或者使用绝对路径
"/Users/me/claude-code-hooks/security.ts"
// 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; // 实现输入修改
}
// 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;
}
// 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> {
// 设置环境变量、加载配置等
}
// 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 };
};
// 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 };
};
// 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/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;
}
};
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}`,
};
}
};
// 分步骤执行,便于定位问题
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 };
};
答案:
// 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 };
};
答案:
// 配置顺序决定执行顺序
// 后执行的 Hook 覆盖先执行的
// 如果 hook1 返回 { continue: false }
// hook2 不会执行(因为流程已中断)
// 解决方案:使用 continue: true 让流程继续
// 在各自的 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 实践
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*