# 第六阶段 · 模块六 · 第二节:内置 Hooks 详解
每个内置 Hook 的输入输出是什么?PreToolUse 如何拦截工具调用?PermissionRequest 的工作流程是什么?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ Hooks 系统 │
│ │
│ 内置 Hooks ← 本节 │
│ ├── PreToolUse ──> 工具执行前 │
│ ├── PostToolUse ──> 工具执行后 │
│ └── PermissionRequest ──> 权限请求 │
└─────────────────────────────────────────────────────────────────────┘
用户/AI → 工具调用 → PreToolUse → 工具执行
源码位置:`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;
};
type PreToolUseResult = {
continue?: boolean; // 是否继续执行,默认 true
updatedInput?: object; // 修改后的输入
additionalContext?: string; // 额外上下文
permissionDecision?: {
behavior: 'allow' | 'deny' | 'pause';
reason?: string;
};
};
// 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:如何修改工具输入?
// 通过 updatedInput
return {
continue: true,
updatedInput: {
...input.toolInput,
// 添加默认参数
encoding: 'utf-8',
// 或者修改现有参数
maxLines: 1000,
},
};
问 2:permissionDecision 和 continue 的区别?
// continue: false → 直接阻止,不记录
// permissionDecision: { behavior: 'deny' } → 记录权限拒绝
// → 触发 PermissionDenied Hook
// → 可能触发权限请求提示
工具执行 → PostToolUse → 返回结果给 AI
源码位置:`src/types/hooks.ts` 第 101 行
type PostToolUseInput = {
hookEventName: 'PostToolUse';
toolName: string;
toolInput: Record<string, unknown>;
toolOutput: unknown; // 工具返回结果
wasRateLimited?: boolean;
invocationId: string;
additionalContext?: string;
};
type PostToolUseResult = {
continue?: boolean;
updatedMCPToolOutput?: unknown; // 只对 MCP 工具有效
additionalContext?: string;
};
// 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 };
}
问 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');
}
源码位置:`src/types/hooks.ts` 第 109 行
type PostToolUseFailureInput = {
hookEventName: 'PostToolUseFailure';
toolName: string;
toolInput: Record<string, unknown>;
error: string; // 错误消息
errorCode?: string;
invocationId: string;
additionalContext?: string;
};
// 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 Hook
源码位置:`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;
};
type PermissionRequestResult = {
decision: {
behavior: 'allow' | 'deny' | 'pause';
updatedInput?: object;
updatedPermissions?: PermissionUpdate[];
message?: string;
interrupt?: boolean;
};
};
// 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', // 暂停,等待用户确认
},
};
}
问 1:allow 和 pause 的区别?
// allow → 自动批准,不询问用户
// pause → 暂停,显示提示给用户,等待确认
// deny → 拒绝执行
// interrupt: true → 即使是 pause 也中断流程
用户输入 → UserPromptSubmit → 发送给 AI
type UserPromptSubmitInput = {
hookEventName: 'UserPromptSubmit';
userPrompt: string;
promptElicitation?: {
id: string;
message: string;
options: Array<{
key: string;
label: string;
}>;
};
};
type UserPromptSubmitResult = {
continue?: boolean;
userPrompt?: string; // 修改后的 prompt
additionalContext?: string;
};
答案:
// 在 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 };
}
答案:
// 在 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 };
}
答案:
// 添加日志
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 调试技巧
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*