mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
docs: fix documentation deviations from source code (#220)
* docs: 修正 docs/conversation 文档与源码的偏差(multi-turn/streaming/the-loop) - multi-turn: TranscriptWriter→Project 私有类, 会话路径改用 sanitized-cwd, 补充 StoredCostState.lastDuration 字段, 模型切换改为 setModel(), QueryEngine 状态补全 loadedNestedMemoryPaths/hasHandledOrphanedPermission, 行号改为符号引用 - streaming: STALL_THRESHOLD_MS 10s→30s, 新增 90s 主动空闲看门狗描述, 非流式降级补充 didFallBackToNonStreaming/executeNonStreamingRequest, 行号改为符号引用 - the-loop: 终止条件 7→11, 继续条件重整为 5 组层级结构, max_output_tokens 拆分 escalate/recovery 子阶段, prompt-too-long 拆分 collapse_drain/reactive_compact 子策略, State 类型修正 autoCompactTracking 为可选, 行号改为符号引用 - 全部: 添加 sourceRef 版本锚定(3ec5675) * docs: 修正 docs/extensibility 文档与源码的偏差(custom-agents/hooks/skills) - custom-agents: Verification 模型修正为 inherit, 补充 Plugin Agent 字段限制 (permissionMode/hooks/mcpServers 被安全忽略, isolation 仅 worktree), 加载流程修正为 6 层优先级, 补充 memory snapshot 门控条件 - hooks: 事件数 22→27(补充 Notification), Hook 类型定义位置修正为 3 个文件, 行号改为符号引用, Zod schema 范围修正, 去重键修正为四部分复合键, registerFrontmatterHooks/clearSessionHooks 区分定义位置和调用位置 - skills: 字段数 17→16, 权限层级 4→5(补充 remote canonical auto-allow), SAFE_SKILL_PROPERTIES 28→30, skillUsageTracking 路径修正, 行号改为符号引用 - mcp-protocol: 全部验证通过, 无需修改 - 全部: 添加 sourceRef 版本锚定(3ec5675) * Revert "docs: 修正 docs/extensibility 文档与源码的偏差(custom-agents/hooks/skills)" * docs: 修正 docs/extensibility 文档与源码的偏差(hooks/skills/mcp-protocol) hooks: - 事件数 22→27(补充 Notification 事件) - Hook 类型定义位置修正为 3 个文件分布 (schemas/hooks.ts / types/hooks.ts / utils/hooks/sessionHooks.ts) - Zod schema 引用从硬编码行号改为符号引用 - hookSpecificOutput 表从 6 扩展至 15 个事件 (补全 permissionDecisionReason / PostToolUseFailure / SubagentStart 等) - 去重键从 pluginRoot\0command 修正为四部分复合键 (pluginRoot\0shell\0command\0ifCondition) - 全部硬编码行号改为符号引用以避免版本漂移 skills: - parseSkillFrontmatterFields 字段数 17→16 - SAFE_SKILL_PROPERTIES 属性数 28→30 - checkPermissions 层级 4→5 - 第 2 层描述从"官方市场"修正为"远程 canonical" mcp-protocol: - 配置层级从"三级"修正为 "enterprise 独占或合并 user/project/local + plugin + claude.ai" * docs: 修正 system-prompt.mdx 中 Boundary 章节的层级与可读性 - Boundary 插入条件从 ### 降为 blockquote,不再打断三种分块模式的并列结构 - 表格中 Boundary 缓存策略列补充说明其分割作用 - 新增 Boundary 概念释义(blockquote),解释其分割静态区/动态区以实现全局缓存的设计意图
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: "Hooks 生命周期钩子 - 执行引擎与拦截协议"
|
||||
description: "从源码角度解析 Claude Code Hooks 系统:22 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
|
||||
description: "从源码角度解析 Claude Code Hooks 系统:27 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
|
||||
keywords: ["Hooks", "生命周期钩子", "拦截器", "PreToolUse", "Hook 协议"]
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示 Hook 的执行引擎、匹配机制、返回值协议和生命周期管理 */}
|
||||
|
||||
## 22 种 Hook 事件
|
||||
## 27 种 Hook 事件
|
||||
|
||||
Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完整的 Agent 生命周期:
|
||||
Claude Code 定义了 27 种 Hook 事件(`HOOK_EVENTS` 数组,`src/entrypoints/sdk/coreTypes.ts`),覆盖完整的 Agent 生命周期:
|
||||
|
||||
| 阶段 | 事件 | 触发时机 | 匹配字段 |
|
||||
|------|------|---------|---------|
|
||||
@@ -32,6 +32,7 @@ Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完
|
||||
| | `TaskCompleted` | 任务完成 | — |
|
||||
| **MCP** | `Elicitation` | MCP 服务器请求用户输入 | `mcp_server_name` |
|
||||
| | `ElicitationResult` | Elicitation 结果返回 | `mcp_server_name` |
|
||||
| **通知** | `Notification` | 系统通知事件 | `notification_type` |
|
||||
| **环境** | `ConfigChange` | 配置变更 | `source` |
|
||||
| | `CwdChanged` | 工作目录变更 | — |
|
||||
| | `FileChanged` | 文件变更 | `file_path` |
|
||||
@@ -40,7 +41,11 @@ Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完
|
||||
|
||||
## 6 种 Hook 类型
|
||||
|
||||
Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
|
||||
Hooks 配置支持 6 种执行方式,类型定义分布在 3 个文件中:
|
||||
|
||||
- **可持久化类型**(`command`、`prompt`、`agent`、`http`)— Zod schema 定义在 `src/schemas/hooks.ts`,通过 `z.discriminatedUnion('type', [...])` 声明
|
||||
- **callback 类型** — TypeScript 接口定义在 `src/types/hooks.ts`,用于 SDK 注册的内部 JS 函数
|
||||
- **function 类型** — 定义在 `src/utils/hooks/sessionHooks.ts`,用于运行时动态注册的函数 Hook
|
||||
|
||||
| 类型 | 执行方式 | 适用场景 |
|
||||
|------|---------|---------|
|
||||
@@ -53,7 +58,7 @@ Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
|
||||
|
||||
## 执行引擎:execCommandHook
|
||||
|
||||
`execCommandHook()`(`src/utils/hooks.ts:829-1417`)是命令型 Hook 的执行核心:
|
||||
`execCommandHook()`(`src/utils/hooks.ts`,`execCommandHook` 函数)是命令型 Hook 的执行核心:
|
||||
|
||||
```
|
||||
execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
|
||||
@@ -75,7 +80,7 @@ execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
|
||||
|
||||
### 异步 Hook 的检测协议
|
||||
|
||||
Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`hooks.ts:1199-1246`):
|
||||
Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`isAsyncHookJSONOutput` 检测 + `executeInBackground` 调用):
|
||||
|
||||
```typescript
|
||||
const firstLine = firstLineOf(stdout).trim()
|
||||
@@ -96,7 +101,7 @@ if (isAsyncHookJSONOutput(parsed)) {
|
||||
|
||||
## Hook 输出的 JSON Schema
|
||||
|
||||
同步 Hook 的输出遵循严格的 Zod schema(`src/types/hooks.ts:49-567`):
|
||||
同步 Hook 的输出遵循严格的 Zod schema(`syncHookResponseSchema`,定义在 `src/types/hooks.ts`,`hookJSONOutputSchema` 定义在 `src/schemas/hooks.ts`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -120,16 +125,25 @@ if (isAsyncHookJSONOutput(parsed)) {
|
||||
|
||||
| 事件 | 专有字段 | 作用 |
|
||||
|------|---------|------|
|
||||
| `PreToolUse` | `permissionDecision`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
|
||||
| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
|
||||
| `PreToolUse` | `permissionDecision`, `permissionDecisionReason`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
|
||||
| `PostToolUse` | `additionalContext`, `updatedMCPToolOutput` | 修改 MCP 工具输出 |
|
||||
| `SessionStart` | `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
|
||||
| `PostToolUseFailure` | `additionalContext` | 失败后注入上下文 |
|
||||
| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
|
||||
| `SessionStart` | `additionalContext`, `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
|
||||
| `PermissionRequest` | `decision`(含 `allow`/`deny` 子字段) | 权限请求的 Hook 决策 |
|
||||
| `PermissionDenied` | `retry` | 指示是否重试 |
|
||||
| `SubagentStart` | `additionalContext` | 子 Agent 启动时注入上下文 |
|
||||
| `Elicitation` | `action`, `content` | 控制用户输入对话框 |
|
||||
| `ElicitationResult` | `action`, `content` | Elicitation 结果处理 |
|
||||
| `Notification` | `additionalContext` | 通知事件注入上下文 |
|
||||
| `Setup` | `additionalContext` | 初始化时注入上下文 |
|
||||
| `CwdChanged` | `watchPaths` | 目录变更后更新监控路径 |
|
||||
| `FileChanged` | `watchPaths` | 文件变更后更新监控路径 |
|
||||
| `WorktreeCreate` | `worktreePath` | Worktree 创建通知 |
|
||||
|
||||
## Hook 匹配机制:getMatchingHooks
|
||||
|
||||
`getMatchingHooks()`(`hooks.ts:1685-1956`)负责从所有来源中查找匹配的 Hook:
|
||||
`getMatchingHooks()`(`src/utils/hooks.ts`,`getMatchingHooks` 函数)负责从所有来源中查找匹配的 Hook:
|
||||
|
||||
### 多来源合并
|
||||
|
||||
@@ -143,7 +157,7 @@ getHooksConfig()
|
||||
|
||||
### 匹配规则
|
||||
|
||||
`matcher` 字段支持三种模式(`matchesPattern()`, `hooks.ts:1428-1463`):
|
||||
`matcher` 字段支持三种模式(`matchesPattern()` 函数,`src/utils/hooks.ts`):
|
||||
|
||||
```
|
||||
"Write" → 精确匹配
|
||||
@@ -154,7 +168,7 @@ getHooksConfig()
|
||||
|
||||
### if 条件过滤
|
||||
|
||||
Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`hooks.ts:1472-1503`)预编译匹配器:
|
||||
Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`src/utils/hooks.ts`,`prepareIfConditionMatcher` 函数)预编译匹配器:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -169,11 +183,11 @@ Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditio
|
||||
|
||||
### Hook 去重
|
||||
|
||||
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按 `pluginRoot\0command` 做 Map 去重,保留**最后合并的层级**。
|
||||
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按四部分复合键做 Map 去重:`${pluginRoot}\0${shell}\0${command}\0${ifCondition}`(由 `hookDedupKey()` 函数构建),保留**最后合并的层级**。
|
||||
|
||||
## 工作区信任检查
|
||||
|
||||
**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()`, `hooks.ts:286-296`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
|
||||
**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()` 函数,`src/utils/hooks.ts`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
|
||||
|
||||
```typescript
|
||||
// 交互模式下,所有 Hook 要求信任
|
||||
@@ -226,13 +240,13 @@ SDK 非交互模式下信任是隐式的(`getIsNonInteractiveSession()` 为 tr
|
||||
|
||||
## Session Hook 的生命周期
|
||||
|
||||
Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(`runAgent.ts:567-575`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()` 清理。
|
||||
Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(调用位置:`src/tools/AgentTool/runAgent.ts`;定义位置:`src/utils/hooks/registerFrontmatterHooks.ts`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()`(定义位置:`src/utils/hooks/sessionHooks.ts`)清理。
|
||||
|
||||
```typescript
|
||||
// runAgent.ts:567 — 注册 agent 的前置 Hook
|
||||
// runAgent.ts — 注册 agent 的前置 Hook
|
||||
registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...)
|
||||
|
||||
// runAgent.ts:820 — finally 块清理
|
||||
// runAgent.ts — finally 块清理
|
||||
clearSessionHooks(rootSetAppState, agentId)
|
||||
```
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "
|
||||
```
|
||||
settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } }
|
||||
↓
|
||||
getAllMcpConfigs() ← 合并 user/project/local 三级配置
|
||||
getAllMcpConfigs() ← enterprise 独占或合并 user/project/local + plugin + claude.ai
|
||||
↓
|
||||
useManageMCPConnections() ← React Hook 管理连接生命周期
|
||||
↓
|
||||
|
||||
@@ -48,7 +48,7 @@ Skill 的核心洞见:**复杂任务的关键不在代码逻辑,而在 Promp
|
||||
1. `readdir` 扫描目录 → 仅保留 `isDirectory()` 或 `isSymbolicLink()` 的条目
|
||||
2. 在每个子目录中查找 `SKILL.md`,未找到则跳过
|
||||
3. `parseFrontmatter()` 解析 YAML 头部,提取 `whenToUse`、`allowedTools`、`context` 等字段
|
||||
4. `parseSkillFrontmatterFields()`(第 185 行)统一解析 17 个 frontmatter 字段
|
||||
4. `parseSkillFrontmatterFields()`(第 185 行)统一解析 16 个 frontmatter 字段
|
||||
5. `createSkillCommand()`(第 270 行)构造 `Command` 对象
|
||||
|
||||
**去重机制**:使用 `realpath()` 解析符号链接获得规范路径(`getFileIdentity`,第 118 行),避免通过符号链接或重叠父目录导致的重复加载。
|
||||
@@ -94,7 +94,7 @@ shell: ["bash"] # Shell 执行环境
|
||||
---
|
||||
```
|
||||
|
||||
解析后有 17 个字段被提取,其中 `allowedTools`、`model`、`effort` 在执行时动态修改 `toolPermissionContext`。
|
||||
解析后有 16 个字段被提取,其中 `allowedTools`、`model`、`effort` 在执行时动态修改 `toolPermissionContext`。
|
||||
|
||||
## 两条执行路径:Inline vs Fork
|
||||
|
||||
@@ -128,12 +128,12 @@ Fork 模式适用于需要强隔离的场景(如长时间运行的审查任务
|
||||
|
||||
## 权限模型:Safe Properties 白名单
|
||||
|
||||
`checkPermissions()`(第 433 行)实现了一个四层权限检查:
|
||||
`checkPermissions()`(第 433 行)实现了一个五层权限检查:
|
||||
|
||||
```
|
||||
1. Deny 规则匹配(支持精确匹配和 prefix:* 通配符)
|
||||
↓ 未命中
|
||||
2. 官方市场 Skill 自动放行(plugin + isOfficialMarketplaceName)
|
||||
2. 远程 canonical Skill 自动放行(EXPERIMENTAL_SKILL_SEARCH + USER_TYPE === 'ant')
|
||||
↓ 未命中
|
||||
3. Allow 规则匹配
|
||||
↓ 未命中
|
||||
@@ -142,7 +142,7 @@ Fork 模式适用于需要强隔离的场景(如长时间运行的审查任务
|
||||
5. Ask 用户确认(附带精确匹配和前缀匹配两条建议规则)
|
||||
```
|
||||
|
||||
**Safe Properties**(`SAFE_SKILL_PROPERTIES`,第 876 行)是一个包含 28 个属性名的白名单。任何不在白名单中的**有意义的属性值**(排除 `undefined`、`null`、空数组、空对象)都会触发权限请求。这是**正向安全**设计——未来新增的属性默认需要权限。
|
||||
**Safe Properties**(`SAFE_SKILL_PROPERTIES`,第 876 行)是一个包含 30 个属性名的白名单(覆盖 `PromptCommand` 和 `CommandBase` 两个类型的所有安全属性)。任何不在白名单中的**有意义的属性值**(排除 `undefined`、`null`、空数组、空对象)都会触发权限请求。这是**正向安全**设计——未来新增的属性默认需要权限。
|
||||
|
||||
## Prompt 预算:1% 上下文窗口的截断策略
|
||||
|
||||
@@ -205,7 +205,7 @@ score = usageCount × max(0.5^(daysSinceUse / 7), 0.1)
|
||||
```
|
||||
磁盘 SKILL.md
|
||||
↓ parseFrontmatter()
|
||||
↓ parseSkillFrontmatterFields() → 17 个字段
|
||||
↓ parseSkillFrontmatterFields() → 16 个字段
|
||||
↓ createSkillCommand() → Command 对象
|
||||
↓ 去重(realpath + seenFileIds)
|
||||
↓ 条件 Skill → conditionalSkills Map(等待路径匹配激活)
|
||||
@@ -214,7 +214,7 @@ score = usageCount × max(0.5^(daysSinceUse / 7), 0.1)
|
||||
↓ formatCommandsWithinBudget() → 截断后的 Skill 列表注入 System Prompt
|
||||
↓ AI 选择匹配的 Skill
|
||||
↓ SkillTool.validateInput() → 名称校验 + 存在性检查
|
||||
↓ SkillTool.checkPermissions() → 四层权限检查
|
||||
↓ SkillTool.checkPermissions() → 五层权限检查
|
||||
↓ SkillTool.call() → inline 或 fork 执行
|
||||
↓ contextModifier() → 注入 allowedTools + model + effort
|
||||
↓ recordSkillUsage() → 更新使用频率排名
|
||||
|
||||
Reference in New Issue
Block a user