0%

【长文】Claude Code源码分析

基于泄漏的 Claude Code v2.x 源码的完整技术分析文档。涵盖入口启动、代理循环、工具系统、权限安全、上下文管理、记忆系统、多代理架构、MCP 协议集成、遥测安全等模块。

昨天刚刚update完分析Claude code架构的文章,就爆出来Claude code源码泄露,趁这个热度,我们借助Claude 4.6 opus的力量,剖析一下技术架构

一、入口与启动流程

Claude Code 的启动链路从 src/entrypoints/cli.tsx 开始,经过多层快速路径分发、初始化、认证、配置加载,最终进入 REPL 交互循环或 headless 执行模式。整个设计的核心原则是延迟加载——只在需要时才加载模块,最小化冷启动时间。

启动入口:cli.tsx 的快速路径分发

src/entrypoints/cli.tsx 是整个应用的 bootstrap 入口。它的设计哲学是零导入快速路径——所有 import 都是动态的(await import(...)),确保不匹配的路径不会加载任何多余模块。

顶层副作用(在 main() 之前执行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 禁用 corepack 自动 pin(防止污染用户的 package.json)
process.env.COREPACK_ENABLE_AUTO_PIN = '0'

// 2. CCR 容器环境设置 8GB 堆上限
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
process.env.NODE_OPTIONS = `${existing} --max-old-space-size=8192`
}

// 3. L0 消融基线(Ant-only,feature-gated DCE)
// 同时禁用 thinking、compact、auto-memory、background tasks
if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
for (const k of ['CLAUDE_CODE_SIMPLE', 'CLAUDE_CODE_DISABLE_THINKING', ...]) {
process.env[k] ??= '1'
}
}

消融基线必须在 cli.tsx 而非 init.ts 中设置——因为 BashTool/AgentTool/PowerShellTool 在模块导入时就将 DISABLE_BACKGROUND_TASKS 捕获为模块级常量,init() 运行时已经太晚。

快速路径分发树

main() 函数按优先级检查命令行参数,匹配到的路径立即执行并返回,不加载完整 CLI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cli.tsx main()
├── --version / -v / -V → 打印版本号,零导入
├── --dump-system-prompt → 输出渲染后的 system prompt(Ant-only)
├── --claude-in-chrome-mcp → Chrome 扩展 MCP 服务器
├── --chrome-native-host → Chrome Native Messaging Host
├── --computer-use-mcp → Computer Use MCP 服务器(CHICAGO_MCP)
├── --daemon-worker=<kind> → Daemon Worker 进程(DAEMON)
├── remote-control / rc / bridge → Bridge 远程控制模式(BRIDGE_MODE)
│ ├── OAuth 认证检查
│ ├── GrowthBook 门控检查
│ ├── 最低版本检查
│ └── 企业策略 allow_remote_control 检查
├── daemon [subcommand] → Daemon 长驻进程(DAEMON)
├── ps / logs / attach / kill → 后台会话管理(BG_SESSIONS)
│ └── --bg / --background
├── new / list / reply → 模板任务命令(TEMPLATES)
├── environment-runner → BYOC 环境运行器(BYOC_ENVIRONMENT_RUNNER)
├── self-hosted-runner → 自托管运行器(SELF_HOSTED_RUNNER)
├── --worktree --tmux → Tmux Worktree 快速路径
├── --bare → 设置 CLAUDE_CODE_SIMPLE=1
└── [默认] → 加载完整 CLI(main.tsx)
├── startCapturingEarlyInput() // 捕获用户在加载期间的输入
└── import('../main.js').main()

设计要点:

  1. --version 零导入:只使用编译时内联的 MACRO.VERSION,不加载任何模块。这是最快的路径。
  2. Bridge 模式的认证前置:OAuth 检查必须在 GrowthBook 门控之前——没有认证,GrowthBook 没有用户上下文,会返回过期的默认值 false。
  3. --bare 的早期设置:在加载完整 CLI 之前设置 CLAUDE_CODE_SIMPLE=1,确保模块求值期间的门控就能生效。
  4. startCapturingEarlyInput():在加载 main.tsx(~789KB)期间捕获用户的键盘输入,避免输入丢失。

init() 初始化函数

src/entrypoints/init.ts 中的 init() 是核心初始化函数,使用 memoize 确保只执行一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
init() 执行序列
│
├── enableConfigs() // 验证并启用配置系统
├── applySafeConfigEnvironmentVariables() // 应用安全的环境变量(trust 之前)
├── applyExtraCACertsFromConfig() // 加载自定义 CA 证书(必须在首次 TLS 握手前)
├── setupGracefulShutdown() // 注册优雅退出处理
│
├── [异步] initialize1PEventLogging() // 初始化第一方事件日志
│ └── onGrowthBookRefresh() → reinitialize1PEventLoggingIfConfigChanged()
├── [异步] populateOAuthAccountInfoIfNeeded() // 填充 OAuth 账户信息
├── [异步] initJetBrainsDetection() // JetBrains IDE 检测
├── [异步] detectCurrentRepository() // GitHub 仓库检测
│
├── initializeRemoteManagedSettingsLoadingPromise() // 远程托管设置
├── initializePolicyLimitsLoadingPromise() // 策略限制
├── recordFirstStartTime() // 记录首次启动时间
│
├── configureGlobalMTLS() // mTLS 配置
├── configureGlobalAgents() // HTTP 代理配置
├── preconnectAnthropicApi() // 预连接 Anthropic API(TCP+TLS 握手重叠)
│
├── [CCR] initUpstreamProxy() // 上游代理(容器环境)
├── setShellIfWindows() // Windows git-bash 设置
├── registerCleanup(shutdownLspServerManager) // LSP 清理注册
├── registerCleanup(cleanupSessionTeams) // Swarm 团队清理注册
└── ensureScratchpadDir() // 创建 Scratchpad 临时目录

关键设计细节:

  1. 安全环境变量分层:applySafeConfigEnvironmentVariables() 只应用不需要 trust 的变量(如 NODE_EXTRA_CA_CERTS)。完整的环境变量(applyConfigEnvironmentVariables())在 trust dialog 之后才应用——防止不受信任的项目配置在用户同意前生效。

  2. CA 证书的时序约束:applyExtraCACertsFromConfig() 必须在 configureGlobalAgents() 之前执行。Bun 通过 BoringSSL 在启动时缓存 TLS 证书存储,如果在首次 TLS 握手之后才加载自定义 CA,连接会失败。

  3. API 预连接:preconnectAnthropicApi() 在 CA 证书和代理配置完成后执行,将 TCP+TLS 握手(~100-200ms)与后续的 action handler 工作(~100ms)重叠。对于 proxy/mTLS/unix/cloud-provider 场景跳过——SDK 的 dispatcher 不会复用全局连接池。

  4. 1P 事件日志的延迟初始化:通过 Promise.all([import('firstPartyEventLogger'), import('growthbook')]) 延迟加载 OpenTelemetry sdk-logs 模块。由于 growthbook.js 此时已在模块缓存中(被 firstPartyEventLogger 导入过),第二次动态导入零成本。

main.tsx 的完整启动序列

src/main.tsx(789KB,最大文件)包含完整的 CLI 启动逻辑。以下是从 main() 到 REPL 渲染的关键阶段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
main.tsx main() 启动序列
│
├── 1. 早期解析
│ ├── eagerLoadSettings() // 解析 --settings、--setting-sources
│ ├── initializeEntrypoint() // 设置 CLAUDE_CODE_ENTRYPOINT
│ └── eagerParseCliFlag() // 提取关键 CLI 标志
│
├── 2. Commander 参数解析
│ ├── program.option('--model')
│ ├── program.option('--permission-mode')
│ ├── program.option('--mcp-config')
│ ├── program.option('--agents')
│ └── ... (~40 个选项)
│
├── 3. init() 调用
│ └── 见上文 init() 序列
│
├── 4. 并行预取(用户还在输入时)
│ ├── initUser() // 用户信息
│ ├── getUserContext() // 用户上下文
│ ├── prefetchSystemContextIfSafe() // 系统上下文
│ ├── getRelevantTips() // 提示信息
│ ├── countFilesRoundedRg() // 文件计数(ripgrep)
│ ├── initializeAnalyticsGates() // 分析门控
│ ├── prefetchOfficialMcpUrls() // MCP URL 预取
│ ├── refreshModelCapabilities() // 模型能力刷新
│ ├── settingsChangeDetector.initialize() // 设置变更检测
│ └── skillChangeDetector.initialize() // 技能变更检测
│
├── 5. MCP 配置加载(与 setup 并行)
│ ├── getClaudeCodeMcpConfigs() // 本地 MCP 配置
│ ├── fetchClaudeAIMcpConfigsIfEligible() // claude.ai MCP 配置
│ └── filterMcpServersByPolicy() // 企业策略过滤
│
├── 6. Setup 流程
│ ├── showSetupScreens() // 首次运行引导
│ │ ├── Trust Dialog // 工作区信任确认
│ │ ├── OAuth / API Key 认证 // 认证流程
│ │ └── Onboarding // 新用户引导
│ ├── initializeTelemetryAfterTrust() // Trust 后初始化遥测
│ └── applyConfigEnvironmentVariables() // 应用完整环境变量
│
├── 7. 后 Trust 初始化
│ ├── loadRemoteManagedSettings() // 加载远程托管设置
│ ├── initializeLspServerManager() // LSP 服务器管理器
│ ├── launchInvalidSettingsDialog() // 设置验证错误对话框
│ └── 后台预取(quota、passes、fastMode、bootstrap)
│
├── 8. 模型与命令加载
│ ├── getCommands() // 加载 ~80 个斜杠命令
│ ├── getAgentDefinitionsWithOverrides() // 加载代理定义
│ └── 模型解析(--model → alias resolution)
│
├── 9. MCP 连接
│ ├── prefetchAllMcpResources() // 预取 MCP 资源
│ └── 连接所有配置的 MCP 服务器
│
└── 10. 渲染
├── createRoot() // 创建 Ink 渲染根
└── root.render(<REPL />) // 渲染 REPL 组件
或 runHeadless() // headless 模式执行

入口点类型识别

initializeEntrypoint() 根据运行环境设置 CLAUDE_CODE_ENTRYPOINT:

入口点 条件 说明
'mcp' claude mcp serve MCP 服务器模式
'claude-code-github-action' CLAUDE_CODE_ACTION=true GitHub Action
'local-agent' 预设的环境变量 本地代理模式
'sdk-cli' 非交互式(-p 参数) SDK/headless 模式
'cli' 交互式(默认) 终端 REPL 模式

四种运行模式

REPL 模式:完整的 React/Ink TUI,包括输入框、消息列表、虚拟滚动、权限对话框、状态栏等。通过 root.render(<REPL />) 启动。

Headless 模式(-p):QueryEngine(src/QueryEngine.ts)管理查询生命周期。不渲染 UI,直接输出结果。支持 --output-format json 结构化输出。

SDK 模式:通过 structuredIO(src/cli/structuredIO.ts)与宿主进程通信。VS Code 扩展、Claude Desktop 等通过此模式集成。

MCP Server 模式:src/entrypoints/mcp.ts 将 Claude Code 自身作为 MCP 服务器暴露,允许其他 MCP 客户端调用其工具。

配置加载层级

配置文件按优先级从低到高加载:

层级 路径 说明
1. 默认值 硬编码 内置默认配置
2. 全局 ~/.claude/settings.json 用户全局设置
3. 本地全局 ~/.claude/settings.local.json 用户本地设置(gitignored)
4. 企业 /etc/claude-code/settings.json 企业管理员设置
5. 项目 .claude/settings.json 项目级设置(checked in)
6. 项目本地 .claude/settings.local.json 项目本地设置(gitignored)
7. CLI 参数 --settings <path> 命令行指定的设置文件
8. 远程托管 API 获取 远程托管设置(每小时轮询)

--setting-sources 参数可以控制加载哪些来源——企业环境中可以只加载 managed 来源,忽略用户和项目设置。

认证流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
认证检查
├── OAuth Token 存在?
│ ├── 是 → 检查 token 有效性
│ │ ├── 有效 → 使用 OAuth
│ │ └── 过期 → 刷新 token
│ │ ├── 刷新成功 → 使用新 token
│ │ └── 刷新失败 → 重新授权
│ └── 否 → 检查 API Key
│ ├── ANTHROPIC_API_KEY 存在 → 使用 API Key
│ ├── Bedrock 配置 → AWS 认证
│ ├── Vertex 配置 → GCP 认证
│ └── 无认证 → 引导用户登录
│ ├── /login 命令
│ └── OAuth 浏览器授权流程

OAuth 优先于 API Key:OAuth token 支持 user:profile scope,可以获取用户信息用于 GrowthBook 定向和遥测。API Key 用户缺少这些能力。

--bare 模式的性能优化

--bare(或 CLAUDE_CODE_SIMPLE=1)是性能优化模式,跳过所有非必要的启动工作:

跳过的内容 节省
后台预取(quota、passes、fastMode) ~200ms 网络
claude.ai MCP 代理服务器 6-14s/服务器
自动发现的 MCP(.mcp.json、用户设置) ~100ms I/O
Skill 目录遍历 ~50ms I/O
设置/技能变更检测器 ~30ms
Auto Memory 后台 LLM 调用
Hooks 子进程开销
LSP 服务器 进程开销
Plugin 同步 网络 I/O

实测到达首次 API 请求的时间减少约 14%。适用于脚本化的 -p 调用——这些调用没有"用户正在输入"的窗口来隐藏预取工作。

启动性能追踪

profileCheckpoint() 在关键节点记录时间戳,用于启动性能分析:

1
2
3
4
5
6
7
8
9
10
cli_entry                    → CLI 入口
init_function_start → init() 开始
init_configs_enabled → 配置系统就绪
init_safe_env_vars_applied → 安全环境变量已应用
init_after_graceful_shutdown → 优雅退出已注册
init_after_1p_event_logging → 1P 日志已初始化
init_network_configured → 网络(mTLS + proxy)已配置
init_function_end → init() 完成
action_commands_loaded → 命令和代理已加载
cli_after_main_complete → main() 完成

tengu_timer 事件在 Ink 根创建后立即记录启动时间——在任何阻塞对话框(trust/OAuth/onboarding/resume-picker)之前。旧的实现在 REPL 首次渲染时记录,p99 达到 ~70s(被对话框等待时间主导)。

事件循环阻塞检测

启动后注册一个事件循环阻塞检测器——当主线程被阻塞超过 500ms 时记录日志。这用于诊断启动卡顿和运行时性能问题。

1
2
// Event loop stall detector — logs when the main thread is blocked >500ms
void initializeEventLoopStallDetector()

二、代理循环(Agent Loop)核心

Claude Code 的心脏是 src/query.ts 中的 query() 异步生成器函数。它实现了一个工具增强的 ReAct 循环:用户输入 → 组装 system prompt → 调用 Claude API → 流式解析响应 → 如果包含 tool_use 则执行工具并将结果追加到消息列表 → 循环回 API 调用,直到模型返回纯文本。

整个循环被封装为一个 AsyncGenerator,通过 yield 向调用者(TUI 渲染层)逐条推送事件流。这个设计使得 UI 可以在流式传输过程中实时渲染模型输出和工具执行进度,而不需要等待完整响应。

循环状态机

query() 函数的核心是一个 while (true) 无限循环,每次迭代代表一次完整的 API 请求-响应-工具执行周期。循环通过一个 State 类型管理跨迭代的可变状态:

1
2
3
4
5
6
7
8
9
10
11
12
type State = {
messages: Message[] // 完整消息历史
toolUseContext: ToolUseContext // 工具执行上下文(工具列表、权限、abort 信号等)
autoCompactTracking: AutoCompactTrackingState // 自动压缩追踪(是否已压缩、轮次计数器)
maxOutputTokensRecoveryCount: number // max_output_tokens 恢复计数(最多 3 次)
hasAttemptedReactiveCompact: boolean // 是否已尝试响应式压缩(防止无限重试)
maxOutputTokensOverride: number | undefined // 输出 token 上限覆盖(升级到 ESCALATED_MAX_TOKENS)
pendingToolUseSummary: Promise<...> // 待处理的工具使用摘要(Haiku 异步生成)
stopHookActive: boolean | undefined // Stop Hook 是否活跃(阻止循环终止)
turnCount: number // 当前轮次计数
transition: Continue | undefined // 上一次迭代的继续原因(用于测试断言恢复路径)
}

设计哲学:State 在每次迭代开头被解构为局部变量(只读),在 continue 站点通过 state = { ... } 整体替换(而非 9 个独立赋值)。这保证了状态转换的原子性——不会出现半更新的中间状态。

循环的终止条件由 Terminal 类型表示,包含 reason 字段标识退出原因:

Terminal Reason 触发条件
'completed' 模型返回纯文本,无 tool_use
'aborted_streaming' 用户在流式传输中按 Ctrl+C
'aborted_tools' 用户在工具执行中按 Ctrl+C
'model_error' API 调用抛出异常(非 413/max_output_tokens)
'prompt_too_long' 413 错误且所有恢复策略均失败
'image_error' 图片大小/格式错误
'max_turns' 达到 maxTurns 限制
'hook_stopped' PreToolUse Hook 返回 preventContinuation
'stop_hook_prevented' Stop Hook 返回 preventContinuation
'blocking_limit' 上下文硬上限阻塞

每次迭代的完整流程

迭代流程较长,拆分为三个阶段展示:预处理 → 流式调用 → 后处理。

阶段一:消息预处理与 API 调用
阶段二:流式解析与错误恢复
阶段三:Hook 检查与循环决策

循环入口的双层封装

query() 实际上是一个薄包装器,真正的循环逻辑在 queryLoop() 中:

1
2
3
4
5
6
7
8
9
export async function* query(params: QueryParams): AsyncGenerator<...> {
const consumedCommandUuids: string[] = []
const terminal = yield* queryLoop(params, consumedCommandUuids)
// 只有正常返回时才执行——throw 和 .return() 都会跳过
for (const uuid of consumedCommandUuids) {
notifyCommandLifecycle(uuid, 'completed')
}
return terminal
}

这个双层设计的目的是命令生命周期追踪:consumedCommandUuids 收集本轮消费的排队命令(slash commands、task notifications),只有循环正常完成时才标记为 'completed'。如果循环因错误或中断退出,命令保持未完成状态,下次可以重试。

queryLoop 的初始化

循环开始前,queryLoop() 执行以下初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 解构不可变参数(循环内不会重新赋值)
const { systemPrompt, userContext, systemContext, canUseTool,
fallbackModel, querySource, maxTurns, skipCacheWrite } = params

// 2. 初始化可变状态
let state: State = {
messages: params.messages,
toolUseContext: params.toolUseContext,
maxOutputTokensOverride: params.maxOutputTokensOverride,
// ... 其余字段初始化为默认值
}

// 3. Token Budget 追踪器(feature-gated)
const budgetTracker = feature('TOKEN_BUDGET') ? createBudgetTracker() : null

// 4. Task Budget 跨压缩追踪
// 未压缩时 undefined(服务端可以看到完整历史自行计算)
// 压缩后递减(服务端只看到摘要,无法计算已消耗量)
let taskBudgetRemaining: number | undefined = undefined

// 5. 快照不可变的环境/statsig/会话状态
const config = buildQueryConfig()

// 6. 启动 Memory Prefetch(每用户轮次一次,使用 `using` 自动清理)
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
state.messages, state.toolUseContext
)

using 关键字(TC39 Explicit Resource Management 提案)确保 pendingMemoryPrefetch 在生成器退出时(无论正常、异常还是 .return())都会被 dispose,触发遥测记录和资源释放。

System Prompt 的动态组装

src/constants/prompts.ts 中的 getSystemPrompt() 函数负责组装完整的系统提示。它返回一个 string[],每个元素是一个独立的 prompt section。组装分为静态部分和动态部分,中间由 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 标记分隔——这个边界是 Prompt Cache 优化的关键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
getSystemPrompt() 组装顺序
│
├── 🔒 静态内容(scope: global,跨用户可缓存)
│ ├── ① getSimpleIntroSection() — 身份声明 + CYBER_RISK_INSTRUCTION
│ ├── ② getSimpleSystemSection() — 系统行为规则(工具调用规范、XML 标签使用等)
│ ├── ③ getSimpleDoingTasksSection() — 编码任务指导(文件编辑策略、测试要求等)
│ ├── ④ getActionsSection() — 操作安全指导(可逆性评估、爆炸半径控制)
│ ├── ⑤ getUsingYourToolsSection() — 工具使用指导(并行调用、搜索策略等)
│ ├── ⑥ getSimpleToneAndStyleSection() — 语气风格(简洁、专业、不道歉)
│ └── ⑦ getOutputEfficiencySection() — 输出效率(避免冗余、倒金字塔结构)
│
├── ═══ SYSTEM_PROMPT_DYNAMIC_BOUNDARY ═══ ← Prompt Cache 分界线
│
└── 🔄 动态内容(scope: session,每次可能变化)
├── ⑧ getSessionSpecificGuidanceSection() — 会话特定指导(恢复会话时的上下文提示)
├── ⑨ loadMemoryPrompt() — CLAUDE.md 四级加载 + memdir 结构化记忆
├── ⑩ getAntModelOverrideSection() — Ant 用户模型覆盖(内部员工专用 prompt)
├── ⑪ computeSimpleEnvInfo() — 环境信息(CWD / OS / Git / 模型名 / 知识截止日期)
├── ⑫ getLanguageSection() — 用户语言偏好
│ + getOutputStyleSection() — 输出风格(Markdown / 代码块偏好)
├── ⑬ getMcpInstructionsSection() — MCP 服务器指令(服务器级 + 工具级)
└── ⑭ getScratchpadInstructions() — Scratchpad 临时目录指引
+ getFunctionResultClearingSection() — 工具结果自动清理提示
+ SUMMARIZE_TOOL_RESULTS — 模型侧结果摘要指令
+ getProactiveSection() — KAIROS 自主代理行为指令(feature-gated)

关键设计细节:

  1. CYBER_RISK_INSTRUCTION 被硬编码在 getSimpleIntroSection 的最前面,由 Safeguards 团队维护,定义了安全边界——允许授权安全测试、CTF 挑战,拒绝破坏性技术、DoS 攻击、供应链攻击。这是整个 system prompt 中唯一不可被任何配置覆盖的部分。

  2. Ant 用户差异化:process.env.USER_TYPE === 'ant' 分支为 Anthropic 内部员工提供了更详细的 prompt。例如代码风格部分增加了"默认不写注释"、"完成前验证"等指令;输出效率部分替换为更详细的"用户沟通"指南(流畅散文、倒金字塔结构、避免语义回溯)。这个环境变量是 Bun 的 --define 编译时常量——在外部构建中被替换为 false,相关分支被 DCE 消除。

  3. 模型代号体系:FRONTIER_MODEL_NAME = 'Claude Opus 4.6',模型 ID 映射为 { opus: 'claude-opus-4-6', sonnet: 'claude-sonnet-4-6', haiku: 'claude-haiku-4-5-20251001' }。源码中有 @[MODEL LAUNCH] 注释标记需要在新模型发布时更新的位置。getKnowledgeCutoff() 为每个模型返回不同的知识截止日期(Opus 4.6: May 2025, Sonnet 4.6: August 2025)。

  4. Undercover 模式:当 isUndercover() 返回 true 时,computeSimpleEnvInfo() 中所有模型名称/ID 引用被抑制,getAntModelOverrideSection() 返回 null。Undercover 模式在非内部仓库中自动激活,防止 Anthropic 员工在公开仓库中泄露内部模型代号。DCE 注释强调 process.env.USER_TYPE === 'ant' 必须在每个调用点内联(不能提升为 const),否则 bundler 无法常量折叠。

  5. Scratchpad 目录:getScratchpadInstructions() 为每个会话提供一个隔离的临时目录,替代 /tmp。这避免了多会话间的文件冲突,且不需要权限提示。

  6. Function Result Clearing:getFunctionResultClearingSection() 告知模型旧的工具结果会被自动清理(Cached Micro Compact),最近 N 个结果始终保留。这引导模型在响应中记录重要信息,而非依赖工具结果的持久存在。

  7. KAIROS 自主代理模式:当 feature('KAIROS') 或 feature('PROACTIVE') 激活时,getProactiveSection() 注入完整的自主代理行为指令——包括 tick 处理、Sleep 节奏控制、首次唤醒行为、偏向行动原则。这是一个完全不同的 system prompt 范式。

消息预处理管线

在 API 调用之前,消息经过一条完整的预处理管线。每一层都是独立的、可组合的变换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
消息预处理管线(按执行顺序)

📋 原始消息 messages[]
│
├─ 1. applyToolResultBudget() 裁剪大工具结果 → 持久化到磁盘,替换为预览 + 文件路径引用
│ 豁免:FileReadTool(Infinity)、最近一轮结果
│
├─ 2. snipCompact() 从最旧消息开始替换为 "[message snipped]" [feature: HISTORY_SNIP]
│ 保留最近 4 轮对话,< 1000 tokens 的消息不裁剪
│
├─ 3. microcompact() 合并冗余工具结果:FILE_UNCHANGED_STUB、空结果移除、相似结果合并
│ Cached MC 按 tool_use_id 操作,对内容替换透明
│
├─ 4. contextCollapse() 折叠旧的工具调用+结果对为摘要占位符 [feature: CONTEXT_COLLAPSE]
│ 保留最近 N 轮、错误结果、文件修改结果不折叠
│
├─ 5. autoCompact() token 使用率 > 80% 时触发 LLM 摘要压缩,目标降至 50%
│ 熔断器:压缩后至少 3 轮才能再次压缩
│
├─ 6. prependUserContext() 注入用户上下文(打开的文件、git 状态、选中的代码等)
│
├─ 7. normalizeMessages() 合并连续同角色消息、移除空消息、注入 cache_control 标记
│
└─▶ 📡 Claude API

设计原理:轻量级操作在前(裁剪、合并),重量级操作在后(LLM 摘要)。
如果前面的操作已将 token 数降到阈值以下,后面的操作就不会触发。

applyToolResultBudget 的详细机制:

当工具结果总大小超过预算时,按优先级裁剪最旧的大结果,将其替换为磁盘文件引用。关键细节:

  • maxResultSizeChars 为 Infinity 的工具(如 Read)被豁免,因为持久化 Read 结果会创建循环引用(Read→file→Read)
  • 替换记录通过 contentReplacementState 持久化,支持会话恢复时重放
  • 只有 agent:* 和 repl_main_thread 来源的查询持久化替换记录,临时的 runForkedAgent 调用不持久化
  • 运行在 microcompact 之前——Cached MC 按 tool_use_id 操作(不检查内容),所以内容替换对它透明

管线执行顺序的设计原理:轻量级操作在前(裁剪、合并),重量级操作在后(LLM 摘要)。如果前面的操作已经将 token 数降到阈值以下,后面的操作就不会触发。Context Collapse 在 autoCompact 之前运行——如果 Collapse 已经释放了足够空间,就不需要昂贵的 LLM 摘要。

流式响应解析与工具执行

API 调用通过 queryModelWithStreaming() 发起,返回一个异步迭代器。循环逐条处理流式消息,同时进行工具执行:

backfillObservableInput 的精妙设计:

在 yield 给调用者之前,tool_use block 的 input 会被 backfillObservableInput() 处理——添加派生字段(如文件工具展开 file_path)。但这个操作在克隆的 message 上进行,原始 message 不被修改。原因是原始 message 会被 push 到 assistantMessages 并在下一次迭代中发送给 API——如果修改了原始 message,字节会不匹配,破坏 prompt cache。

withheld 错误机制:

可恢复的错误(prompt-too-long、max_output_tokens、media-size-error)在流式传输中被"扣留"(withheld),不立即 yield 给调用者。它们仍然被 push 到 assistantMessages,以便后续的恢复检查可以找到它们。只有当所有恢复策略都失败时,错误才浮出。这个设计避免了 UI 闪烁——用户不会看到一个错误消息然后立即消失。

StreamingToolExecutor 并发控制

StreamingToolExecutor(src/services/tools/StreamingToolExecutor.ts)是并行工具执行的核心。它实现了一个带并发控制的执行队列:

并发控制规则:

1
2
3
4
5
6
7
private canExecuteTool(isConcurrencySafe: boolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing')
return (
executingTools.length === 0 || // 无工具在执行
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe)) // 全部是并发安全的
)
}
  • 并发安全工具(isConcurrencySafe=true,如 Read、Glob、Grep)可以与其他并发安全工具并行执行
  • 非并发安全工具(如 Write、Edit、Bash)必须独占执行——队列中遇到非并发安全工具时停止调度
  • isConcurrencySafe 的判断需要先 safeParse 输入——如果 parse 失败,保守地假设不安全

Bash 错误级联取消:

1
2
3
4
5
if (tool.block.name === BASH_TOOL_NAME) {
this.hasErrored = true
this.erroredToolDescription = this.getToolDescription(tool)
this.siblingAbortController.abort('sibling_error')
}

Bash 工具错误会通过 siblingAbortController 级联取消所有兄弟工具。原因是 Bash 命令常有隐式依赖链(如 mkdir 失败 → 后续命令无意义)。但 Read/WebFetch 等独立工具的错误不会级联——一个文件读取失败不应该取消其他文件的读取。

中断行为分层:

1
2
3
4
5
6
7
8
9
10
11
12
private getAbortReason(tool): 'sibling_error' | 'user_interrupted' | 'streaming_fallback' | null {
if (this.discarded) return 'streaming_fallback' // 模型降级,丢弃所有
if (this.hasErrored) return 'sibling_error' // Bash 错误级联
if (this.toolUseContext.abortController.signal.aborted) {
if (signal.reason === 'interrupt') { // 用户提交新消息
return this.getToolInterruptBehavior(tool) === 'cancel'
? 'user_interrupted' : null // 'block' 工具继续运行
}
return 'user_interrupted' // 硬取消(Escape)
}
return null
}

interruptBehavior 为 'cancel' 的工具(如 Read)在用户中断时被取消,'block' 的工具(如 Write)继续运行直到完成。这防止了文件写入被中途打断导致数据损坏。

Progress 消息的即时传递:

工具执行过程中的 progress 类型消息(如 Bash 命令的实时输出)被存储在 pendingProgress 数组中,通过 progressAvailableResolve 信号唤醒 getRemainingResults() 的等待循环。这保证了用户可以实时看到长时间运行的命令输出,而不需要等待命令完成。

结果顺序保证:

getCompletedResults() 按工具接收顺序遍历——如果一个非并发安全工具还在执行,即使后面的并发安全工具已完成,也不会 yield 后面的结果。这保证了消息顺序与模型的 tool_use 顺序一致。

错误恢复机制

循环内置了多层错误恢复,形成一个优先级递减的恢复链:

prompt-too-long 恢复的三级策略:

  1. Context Collapse drain(最优先):释放已暂存的上下文折叠。如果上一次迭代的 transition.reason 已经是 'collapse_drain_retry'(说明 drain 后重试仍然 413),则跳过直接进入下一级。
  2. Reactive Compact(次优先):完整的 LLM 压缩摘要。hasAttemptedReactiveCompact 标志防止无限重试。
  3. 错误浮出:两者都失败时,yield 扣留的错误消息,执行 StopFailure Hooks,返回 Terminal: prompt_too_long。

关键防护:恢复失败时不执行 Stop Hooks——模型从未产生有效响应,Hooks 没有有意义的内容可评估。运行 Stop Hooks 会创建死亡螺旋:error → hook blocking → retry → error → …(Hook 每次注入更多 token)。

max_output_tokens 的两阶段恢复:

  1. 升级重试(tengu_otk_slot_v1 特性标志):如果使用了默认的 8K 上限且被截断,直接以 64K(ESCALATED_MAX_TOKENS)重试同一请求——无 meta 消息、无多轮对话。这是一次性的(由 maxOutputTokensOverride === undefined 守卫)。
  2. 多轮恢复:如果升级后仍被截断,注入 "Output token limit hit. Resume directly—no apology, no recap. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces." 消息,最多 3 次。

模型降级的完整清理:

降级时需要清理所有与旧模型相关的状态:

  • 清空 assistantMessages、toolResults、toolUseBlocks
  • 丢弃旧的 StreamingToolExecutor,创建新的
  • 为孤立的 assistant messages yield tombstone 事件(UI 移除这些消息)
  • Ant 用户额外执行 stripSignatureBlocks()——thinking 签名是模型绑定的,将 Capybara 的签名发送给 Opus 会导致 400 错误

Token Budget 与 Task Budget

Claude Code 支持两种不同层面的 token 预算机制:

维度 Token Budget Task Budget
层面 客户端级别 API 级别
触发方式 用户指定(如 "+500k") output_config.task_budget
实现 createBudgetTracker() task-budgets-2026-03-13 beta
检查时机 每次迭代结束 API 请求参数
跨压缩 重置(新的追踪周期) taskBudgetRemaining 递减
自动继续 注入 nudge 消息 服务端控制

Token Budget 的 diminishing returns 检测:当连续多次继续但输出 token 增长率下降时,提前停止——避免模型陷入重复输出的死循环。

Task Budget 的跨压缩追踪:taskBudgetRemaining 在循环外声明(不在 State 上),避免触碰 7 个 continue 站点。未压缩时为 undefined(服务端可以看到完整历史自行计算),压缩后递减(服务端只看到摘要,无法计算已消耗量)。

轮次间的附件注入

每次工具执行完成后、进入下一次迭代前,循环注入多种附件消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 排队命令快照(slash commands、task notifications)
const queuedCommandsSnapshot = getCommandsByMaxPriority(sleepRan ? 'later' : 'next')
.filter(cmd => {
if (isSlashCommand(cmd)) return false // Slash 命令走 processSlashCommand
if (isMainThread) return cmd.agentId === undefined // 主线程只消费无 agentId 的命令
return cmd.mode === 'task-notification' && cmd.agentId === currentAgentId // 子代理只消费自己的
})

// 2. 通用附件(文件变更检测、编辑预览等)
for await (const attachment of getAttachmentMessages(...)) {
yield attachment
}

// 3. Memory Prefetch 消费(零等待,未就绪则跳过)
if (pendingMemoryPrefetch?.settledAt !== null && consumedOnIteration === -1) {
const memoryAttachments = filterDuplicateMemoryAttachments(
await pendingMemoryPrefetch.promise,
toolUseContext.readFileState // 过滤模型已 Read/Write/Edit 的文件
)
}

// 4. Skill Discovery Prefetch 消费
if (pendingSkillPrefetch) {
const skillAttachments = await skillPrefetch.collectSkillDiscoveryPrefetch(pendingSkillPrefetch)
}

// 5. 刷新工具列表(新连接的 MCP 服务器)
if (updatedToolUseContext.options.refreshTools) {
const refreshedTools = updatedToolUseContext.options.refreshTools()
}

命令队列的代理隔离:队列是进程全局单例,被协调器和所有进程内子代理共享。每个循环只消费属于自己的命令——主线程消费 agentId === undefined 的命令,子代理消费 agentId === currentAgentId 的命令。用户 prompt(mode: 'prompt')始终只发给主线程。

Sleep 工具的队列刷新:当 SleepTool 在本轮执行过(sleepRan = true),命令队列的优先级阈值从 'next' 提升到 'later'——这允许 KAIROS 自主代理在 Sleep 后消费低优先级的任务通知。

Feature Gate 系统

源码大量使用 feature('FLAG_NAME') 进行编译时门控。这是 Bun 的 bun:bundle 提供的编译时常量折叠机制——在 npm 发布的外部构建中,所有 feature() 调用被替换为 false,相关代码被死代码消除(DCE)。这意味着 108 个内部模块在外部构建中完全不存在:

1
2
3
4
5
6
7
8
// 编译时门控示例——feature() 只能出现在 if/ternary 条件中
const reactiveCompact = feature('REACTIVE_COMPACT')
? require('./services/compact/reactiveCompact.js')
: null

const contextCollapse = feature('CONTEXT_COLLAPSE')
? require('./services/contextCollapse/index.js')
: null

DCE 约束:feature() 调用必须出现在 if 或三元表达式的条件位置——不能赋值给变量后再判断,否则 Bun 的 tree-shaking 无法识别。同样,process.env.USER_TYPE === 'ant' 必须在每个调用点内联,不能提升为 const。

已识别的 Feature Flags 按功能分类:

类别 Flags
上下文管理 REACTIVE_COMPACT, CONTEXT_COLLAPSE, HISTORY_SNIP, CACHED_MICROCOMPACT, TOKEN_BUDGET
多代理 FORK_SUBAGENT, COORDINATOR_MODE, BG_SESSIONS, VERIFICATION_AGENT
自主代理 (KAIROS) KAIROS, KAIROS_BRIEF, KAIROS_CHANNELS, KAIROS_PUSH_NOTIFICATION, KAIROS_GITHUB_WEBHOOKS, PROACTIVE
工具 MONITOR_TOOL, WEB_BROWSER_TOOL, OVERFLOW_TEST_TOOL, BASH_CLASSIFIER, TRANSCRIPT_CLASSIFIER
扩展 EXPERIMENTAL_SKILL_SEARCH, MCP_SKILLS, WORKFLOW_SCRIPTS, AGENT_TRIGGERS, AGENT_TRIGGERS_REMOTE, TEMPLATES
基础设施 BRIDGE_MODE, CHICAGO_MCP, TERMINAL_PANEL, UDS_INBOX, PROMPT_CACHE_BREAK_DETECTION, TEAMMEM

三、工具系统(Tool System)

Claude Code 内置 40+ 工具,通过统一的 Tool 接口和 buildTool() 工厂函数构建。工具系统的设计核心是fail-closed 安全默认值和并发感知的执行模型。

工具接口与 buildTool 工厂

所有工具都通过 src/Tool.ts 中的 Tool 接口定义,并通过 buildTool() 工厂函数构建。Tool 接口定义了 40+ 个方法和属性,核心包括:

方法/属性 类型 说明
name string 工具名称,API 中的 tool name
aliases string[] 向后兼容别名(旧名称仍可匹配权限规则)
inputSchema z.ZodType Zod schema,定义输入参数
call() async generator 工具执行逻辑,yield 中间结果和最终结果
description() async 动态生成工具描述(给用户看,显示在 UI 中)
prompt() async 生成 AI-facing prompt(给模型看,注入 system prompt)
checkPermissions() async 工具级权限检查(返回 allow/deny/ask + updatedInput)
validateInput() async 输入验证(在权限检查之前执行)
isConcurrencySafe() (input) => boolean 是否可并行执行(接收 parsed input)
isReadOnly() boolean 是否只读(影响权限默认值)
isDestructive() boolean 是否不可逆(影响 UI 警告级别)
interruptBehavior() () => 'cancel'|'block' 用户中断时的行为
maxResultSizeChars number 结果超此大小则持久化到磁盘(Infinity 表示豁免)
shouldDefer boolean 是否延迟加载(ToolSearch 模式下不在初始 schema 中)
alwaysLoad boolean 是否始终加载(不受 ToolSearch 影响)
searchHint string ToolSearch 关键词匹配提示(3-10 词)
strict boolean 严格模式(API 层面更严格遵守 schema)
backfillObservableInput() (input) => void 回填可观察输入(不修改原始输入,保护 prompt cache)
preparePermissionMatcher() async 准备权限匹配器(如 Bash(git *) 模式匹配)
toAutoClassifierInput() (input) => string 生成分类器输入(Auto Mode 安全判断)
isSearchOrReadCommand() (input) => {...} 判断是否为搜索/读取操作(UI 折叠显示)
getActivityDescription() (input) => string 生成活动描述(Spinner 显示)
renderToolUseMessage() React 组件 渲染工具调用消息
renderToolResultMessage() React 组件 渲染工具结果消息
renderGroupedToolUse() React 组件 渲染并行工具组

buildTool() 提供安全的默认值(fail-closed 原则):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_input?) => false, // 假设不安全——新工具默认独占执行
isReadOnly: (_input?) => false, // 假设会写入——新工具默认需要权限
isDestructive: (_input?) => false,
checkPermissions: (input, _ctx?) => // 延迟到通用权限系统
Promise.resolve({ behavior: 'allow', updatedInput: input }),
toAutoClassifierInput: (_input?) => '', // 空字符串 → 跳过分类器
userFacingName: (_input?) => '',
}

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}

类型级别的精确性:BuiltTool<D> 是一个条件映射类型——对于每个 defaultable key,如果 D 提供了该方法(required),使用 D 的类型;如果 D 省略了(inherited from Partial<>),使用默认值的类型。这保证了 60+ 个工具的零类型错误。

工具生命周期

每次工具调用经过以下生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
工具调用生命周期
│
├── 1. inputSchema.safeParse(input) // Zod schema 验证
│ └── 失败 → 返回错误给模型
│
├── 2. validateInput(input, context) // 工具级输入验证
│ └── 失败 → 返回验证错误给模型
│
├── 3. PreToolUse Hooks // 用户定义的前置 Hook
│ ├── Command Hook(shell 命令)
│ ├── Prompt Hook(LLM 判断)
│ └── HTTP Hook(远程端点)
│ └── 结果:allow / deny / ask / updatedInput
│
├── 4. hasPermissionsToUseTool() // 规则匹配引擎
│ ├── deny 规则 → 直接拒绝
│ ├── ask 规则 → 进入交互式确认
│ ├── allow 规则 → 检查 Auto Mode 分类器
│ └── 无匹配 → checkPermissions()
│
├── 5. checkPermissions(input, context) // 工具级权限检查
│ └── Bash: 命令解析 + 路径验证 + 沙箱检查
│ └── File: 路径权限 + 工作目录验证
│
├── 6. [交互式确认] // 如果需要用户确认
│ ├── Terminal UI / Bridge UI / Channel UI
│ └── Allow Once / Allow Always / Deny
│
├── 7. call(input, context, ...) // 工具执行
│ ├── yield progress 消息 // 实时进度
│ └── return ToolResult<Output> // 最终结果
│
├── 8. PostToolUse Hooks // 后置 Hook
│ └── 可注入 systemMessage
│
└── 9. mapToolResultToToolResultBlockParam() // 格式化为 API 响应

工具注册与过滤

src/tools.ts 中的 getAllBaseTools() 是所有工具的注册中心。工具列表的组装考虑了多个条件:

Prompt Cache 稳定性是工具排序的核心考量——内置工具必须作为连续前缀,MCP 工具排在后面。如果混合排序,MCP 工具的增减会导致内置工具的位置变化,破坏所有下游的 cache key。uniqBy('name') 保证内置工具在名称冲突时优先——如果 MCP 服务器提供了与内置工具同名的工具,内置版本胜出。

完整工具清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FILE OPERATIONS          SEARCH & DISCOVERY        EXECUTION
═════════════════ ══════════════════════ ══════════
FileReadTool GlobTool BashTool
FileEditTool GrepTool PowerShellTool
FileWriteTool ToolSearchTool
NotebookEditTool INTERACTION
═══════════
WEB & NETWORK AGENT / TASK AskUserQuestionTool
════════════════ ══════════════════ BriefTool
WebFetchTool AgentTool
WebSearchTool SendMessageTool PLANNING & WORKFLOW
TeamCreateTool ════════════════════
MCP PROTOCOL TeamDeleteTool EnterPlanModeTool
══════════════ TaskCreateTool ExitPlanModeTool
MCPTool TaskGetTool EnterWorktreeTool
ListMcpResourcesTool TaskUpdateTool ExitWorktreeTool
ReadMcpResourceTool TaskListTool TodoWriteTool
TaskStopTool
TaskOutputTool SYSTEM
════════
SKILLS & EXTENSIONS ConfigTool
═════════════════════ SkillTool
SkillTool ScheduleCronTool
LSPTool SleepTool
TungstenTool

BashTool 深度分析

BashTool(src/tools/BashTool/BashTool.tsx,157.88KB)是最复杂的工具,包含完整的命令安全分析系统。

命令执行流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BashTool.call(input)
│
├── 1. 命令解析
│ ├── parseCommand() → AST
│ ├── 提取命令名、参数、管道、重定向
│ └── 识别 shell 特殊语法(&&, ||, ;, |)
│
├── 2. 安全分析(bashSecurity.ts, 102.69KB)
│ ├── classifyCommand() → 命令语义分类
│ │ ├── read-only(ls, cat, grep, find, git log)
│ │ ├── write(mkdir, cp, mv, touch)
│ │ ├── destructive(rm, git push --force)
│ │ └── network(curl, wget, ssh)
│ ├── validatePaths() → 路径沙箱验证
│ │ ├── 检查是否在工作目录内
│ │ ├── 检查 additionalWorkingDirectories
│ │ └── 拒绝 /etc, /usr, ~ 等敏感路径
│ ├── validateSedCommand() → sed 命令解析
│ │ ├── 提取 sed 表达式
│ │ ├── 验证目标文件路径
│ │ └── 检查是否为 in-place 编辑(-i)
│ └── checkDestructivePatterns() → 破坏性模式检测
│ ├── rm -rf /
│ ├── git push --force
│ ├── chmod 777
│ └── > /dev/sda
│
├── 3. 沙箱执行
│ ├── macOS: seatbelt sandbox-exec
│ │ └── 限制文件系统、网络、进程访问
│ ├── Linux: 容器隔离
│ └── Windows: 无沙箱(PowerShellTool 替代)
│
├── 4. 进程管理
│ ├── spawn 子进程
│ ├── 实时 stdout/stderr 流式传输(yield progress)
│ ├── 超时控制(默认 120s,可配置)
│ └── 信号处理(SIGTERM → SIGKILL)
│
└── 5. 结果处理
├── exit code 检查
├── 输出截断(超过 maxResultSizeChars)
└── 错误级联(通知 StreamingToolExecutor)

Bash 错误级联:Bash 工具错误会通过 siblingAbortController 级联取消所有兄弟工具。原因是 Bash 命令常有隐式依赖链(如 mkdir 失败 → 后续命令无意义)。但 Read/WebFetch 等独立工具的错误不会级联。

FileEditTool 的 string-replace 策略

FileEditTool(src/tools/FileEditTool/FileEditTool.ts)使用 string-replace 编辑策略而非 diff/patch:

1
2
3
4
5
6
7
// 输入 schema
{
file_path: string, // 目标文件路径
old_string: string, // 要替换的精确文本
new_string: string, // 替换后的文本
replace_all?: boolean, // 是否替换所有匹配(默认 false)
}

设计理由:

  1. 精确匹配:old_string 必须精确匹配文件中的文本(包括空白),避免 diff 的模糊匹配导致的错误编辑
  2. 原子性:如果 old_string 不匹配或匹配多处(且 replace_all=false),操作失败——不会产生部分编辑
  3. Prompt Cache 友好:模型只需要生成变更的部分,不需要重复整个文件内容
  4. 可审计:用户可以清楚看到"什么被替换为什么"

ToolSearch 延迟加载

当工具数量过多时(特别是 MCP 工具),所有工具的 schema 会占用大量上下文窗口。ToolSearch 机制解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ToolSearch 工作流程
│
├── 启动时
│ ├── 标记 shouldDefer=true 的工具为延迟加载
│ ├── 标记 alwaysLoad=true 的工具为始终加载
│ ├── 计算所有 MCP 工具描述的总大小
│ └── 如果总大小 > 上下文窗口的 10% → 自动启用 ToolSearch
│
├── 初始 API 请求
│ ├── 只包含非延迟工具的完整 schema
│ ├── 延迟工具以 defer_loading: true 发送(只有名称和 searchHint)
│ └── ToolSearchTool 本身始终加载
│
├── 模型需要延迟工具时
│ ├── 调用 ToolSearchTool(query="...")
│ ├── 关键词匹配 searchHint + 工具名称
│ └── 返回匹配工具的完整 schema
│
└── 后续请求
└── 已搜索到的工具 schema 持久化在上下文中

MCP 工具的 alwaysLoad:MCP 工具可以通过 _meta['anthropic/alwaysLoad'] 标记为始终加载——用于模型在第一轮就必须看到的关键工具,无需 ToolSearch 往返。

REPL 模式

当 isReplModeEnabled() 为 true 时(Ant-only),REPL_ONLY_TOOLS(包括 Bash、Read、Edit、Glob、Grep 等原始工具)被隐藏,所有操作通过 REPLTool 的 VM 上下文执行。这是一种更高级的工具封装模式——模型不再直接调用文件系统工具,而是在一个持久化的 REPL 环境中执行代码。

工具结果持久化

当工具结果超过 maxResultSizeChars 时,结果被持久化到磁盘,模型收到一个预览 + 文件路径引用:

1
2
3
4
5
6
// maxResultSizeChars 的典型值
MCPTool: 100_000 // 100K 字符
SkillTool: 100_000
LSPTool: 100_000
GrepTool: 30_000 // 搜索结果较小
FileReadTool: Infinity // 豁免——持久化会创建循环引用

FileReadTool 的 maxResultSizeChars = Infinity 是关键设计——如果 Read 结果被持久化到文件,模型需要再次 Read 该文件来获取内容,形成 Read→file→Read 的循环引用。Read 工具通过自身的 fileReadingLimits(maxTokens、maxSizeBytes)自行限制输出大小。

工具 Prompt 的动态生成

每个工具的 prompt() 方法生成 AI-facing 的使用指南,注入到 system prompt 中。Prompt 内容是动态的——根据当前环境、权限模式、可用工具等条件变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BashTool.prompt() 的条件分支示例
async prompt({ getToolPermissionContext, tools }) {
const ctx = await getToolPermissionContext()
let prompt = BASE_BASH_PROMPT

if (ctx.mode === 'bypassPermissions') {
prompt += YOLO_MODE_ADDENDUM // "你可以自由执行任何命令"
}
if (isSandboxingEnabled()) {
prompt += SANDBOX_ADDENDUM // "命令在沙箱中执行,某些操作受限"
}
if (hasGrepTool(tools)) {
prompt += "对于搜索,优先使用 GrepTool 而非 bash grep"
}
return prompt
}

中断行为分层

1
2
3
// interruptBehavior 的语义
'cancel' // 用户中断时立即取消(Read、Grep、Glob 等只读工具)
'block' // 用户中断时继续运行直到完成(Write、Edit 等写入工具)

'block' 行为防止文件写入被中途打断导致数据损坏。当用户按 Escape 时,'cancel' 工具被取消,'block' 工具继续运行。当用户提交新消息时(软中断),只有 interruptBehavior === 'cancel' 的工具被取消。


四、权限与安全系统

Claude Code 实现了一个多层纵深防御的权限模型,从 Hook 前置检查到规则匹配到交互式确认到工具级沙箱,确保每次工具调用都经过严格的安全审查。

四层权限检查流程

src/hooks/useCanUseTool.tsx 中的 useCanUseTool Hook 是权限检查的入口。完整的权限检查流程:

规则匹配引擎

src/utils/permissions/permissions.ts 中的 hasPermissionsToUseTool() 实现了规则匹配。规则来源按优先级排列(高 → 低):

1
2
3
4
5
6
7
8
9
10
规则优先级(高 → 低)
│
├── managed-settings // 企业管理员设置(最高优先级)
├── user // 用户全局设置(~/.claude/settings.json)
├── userLocal // 用户本地设置(~/.claude/settings.local.json)
├── project // 项目设置(.claude/settings.json)
├── projectLocal // 项目本地设置(.claude/settings.local.json)
├── cliArg // CLI 参数(--allowedTools, --disallowedTools)
├── command // 斜杠命令设置
└── session // 会话内用户决策(Allow Always / Deny)

规则值支持多种模式:

模式 示例 说明
精确匹配 Bash、Read、Write 匹配工具名称
带内容匹配 Bash(git:*) 匹配工具名称 + 命令前缀
通配符 Bash(npm *) 通过 preparePermissionMatcher() 预编译
MCP 工具 mcp__github__create_issue 匹配特定 MCP 工具
MCP 服务器前缀 mcp__github 匹配该服务器所有工具

deny 规则的绝对优先级:deny 规则具有最高优先级,任何其他规则都无法覆盖。managed 来源的 ask 规则不能被用户的 allow 规则覆盖——这保证了企业管理员可以强制要求某些工具必须经过审批。

hasPermissionsToUseToolInner 的完整逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
async function hasPermissionsToUseToolInner(tool, input, context) {
// 1a. 检查 deny 规则(整个工具被拒绝)
const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
if (denyRule) return { behavior: 'deny', ... }

// 1b. 检查 ask 规则(整个工具需要确认)
const askRule = getAskRuleForTool(appState.toolPermissionContext, tool)
if (askRule) {
// 特殊情况:沙箱模式下 Bash 可以自动放行
const canSandboxAutoAllow =
tool.name === BASH_TOOL_NAME &&
SandboxManager.isSandboxingEnabled() &&
SandboxManager.isAutoAllowBashIfSandboxedEnabled() &&
shouldUseSandbox(input)
if (!canSandboxAutoAllow) return { behavior: 'ask', ... }
// 否则 fall through 到 checkPermissions
}

// 1c. 工具级权限检查(如 Bash 子命令规则)
const toolPermissionResult = await tool.checkPermissions(parsedInput, context)
// toolPermissionResult.behavior: 'allow' | 'deny' | 'ask' | 'passthrough'

// 2. 检查 allow 规则
const allowRule = getAllowRuleForTool(appState.toolPermissionContext, tool, input)
if (allowRule) return { behavior: 'allow', ... }

// 3. 如果工具说 allow 且没有 ask 规则 → 放行
if (toolPermissionResult.behavior === 'allow') return { behavior: 'allow', ... }

// 4. 默认 → ask
return { behavior: 'ask', ... }
}

Auto Mode 分类器

当权限模式为 auto 时,classifyYoloAction() 使用 LLM 分类器判断工具调用是否安全:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Auto Mode 分类器流程
│
├── 输入构建
│ ├── tool.toAutoClassifierInput(input) // 工具特定的分类器输入
│ │ ├── Bash: 完整命令字符串
│ │ ├── Edit: "path: content"
│ │ └── Write: "path: content"
│ └── 对话上下文(最近的消息摘要)
│
├── 分类器类型
│ ├── Bash Classifier(feature: BASH_CLASSIFIER)
│ │ └── 专门分析 Bash 命令的安全性
│ └── Transcript Classifier(feature: TRANSCRIPT_CLASSIFIER)
│ └── 分析完整对话上下文
│
├── 分类结果
│ ├── { matches: true, confidence: 'high' } → 自动放行
│ ├── { matches: true, confidence: 'low' } → 交互式确认
│ └── { matches: false } → 交互式确认
│
└── Denial Tracking 防死锁
├── recordDenial() 记录每次拒绝
├── 连续拒绝达到 DENIAL_LIMITS 阈值
└── shouldFallbackToPrompting() → 降级为交互式确认

Speculative Classifier:对于 Bash 工具,在显示权限对话框之前,先启动一个 2 秒超时的推测性分类。如果分类器在 2 秒内返回 high-confidence match,直接放行,用户不会看到权限提示。这显著改善了 Auto Mode 的用户体验——大多数安全命令(ls、cat、git status)可以在用户无感知的情况下通过。

Hook 系统

src/utils/hooks.ts(160.62KB,5023 行)实现了完整的 Hook 执行引擎。

Hook 事件类型:

事件 触发时机 可控制
PreToolUse 工具执行前 approve/deny/modify input
PostToolUse 工具执行后 inject systemMessage
PostToolUseFailure 工具执行失败后 通知
PermissionRequest 权限请求时 allow/deny
PermissionDenied 权限被拒绝后 通知
Notification 通用通知 通知
Stop 循环即将停止 blocking/preventContinuation
SubagentStop 子代理即将停止 blocking/preventContinuation
PreCompact 压缩前 customInstructions
PostCompact 压缩后 systemMessage
SessionStart 会话开始 systemMessage
SessionEnd 会话结束 通知
InstructionsLoaded CLAUDE.md 加载后 通知
FileChanged 文件变更后 通知
TaskCreated 任务创建后 通知
Elicitation MCP 请求输入 自动回答

三种 Hook 类型:

类型 实现 通信方式 超时 适用场景
Command spawn 子进程 stdin JSON → stdout JSON/text 10 分钟 本地脚本、CI 集成
Prompt execPromptHook() 模板化 prompt → LLM 响应 取决于 LLM 复杂判断、上下文感知
HTTP execHttpHook() POST JSON → JSON 响应 10 分钟 远程服务、Webhook

Command Hook 的输出解析:

1
2
3
4
5
6
7
8
9
function parseHookOutput(stdout: string) {
const trimmed = stdout.trim()
if (!trimmed.startsWith('{')) {
return { plainText: stdout } // 纯文本模式——内容作为 systemMessage 注入
}
// JSON 模式——验证 Zod schema
// 支持字段:continue, suppressOutput, stopReason, decision,
// systemMessage, permissionDecision, hookSpecificOutput
}

异步 Hook:registerPendingAsyncHook() 支持后台执行的异步 Hook。asyncRewake 模式下,Hook 完成后如果 exit code 为 2(blocking error),通过 enqueuePendingNotification() 唤醒模型。

Hook 来源控制:

  • shouldDisableAllHooksIncludingManaged():禁用所有 Hook(调试模式)
  • shouldAllowManagedHooksOnly():只允许 managed Hook(企业管理)
  • isSourceAdminTrusted():判断 Hook 来源是否为 admin-trusted

resolveHookPermissionDecision 的不变量

src/services/tools/toolHooks.ts 中的 resolveHookPermissionDecision() 封装了一个关键不变量:Hook 的 allow 不能绕过 settings.json 的 deny/ask 规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Hook allow 的处理逻辑
if (hookPermissionResult?.behavior === 'allow') {
const hookInput = hookPermissionResult.updatedInput ?? input

// 如果工具需要用户交互且 Hook 提供了 updatedInput → 视为交互已满足
const interactionSatisfied =
requiresInteraction && hookPermissionResult.updatedInput !== undefined

// 即使 Hook approve,deny/ask 规则仍然生效
const ruleCheck = await checkRuleBasedPermissions(tool, hookInput, toolUseContext)
if (ruleCheck === null) {
// 无规则阻止 → Hook 的 allow 生效
return { decision: hookPermissionResult, input: hookInput }
}
if (ruleCheck.behavior === 'deny') {
// deny 规则覆盖 Hook 的 allow
return { decision: ruleCheck, input: hookInput }
}
// ask 规则 → 仍需交互式确认
}

这个设计防止了安全漏洞:恶意 Hook 不能通过返回 allow 来绕过管理员设置的 deny 规则。

Bash 安全模型

src/tools/BashTool/bashSecurity.ts(102.69KB)实现了 Bash 命令的深度安全分析:

命令语义分类:

分类 示例 权限
只读 ls, cat, grep, find, git log, wc 自动放行
写入 mkdir, cp, mv, touch, git add 需要确认
破坏性 rm -rf, git push --force, chmod 777 强制确认 + 警告
网络 curl, wget, ssh, nc 需要确认
包管理 npm install, pip install, apt-get 需要确认

路径验证(pathValidation.ts,43.93KB):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
路径验证规则
│
├── 工作目录检查
│ ├── 命令中的所有路径必须在 CWD 或 additionalWorkingDirectories 内
│ ├── 符号链接解析后重新检查
│ └── 相对路径解析为绝对路径后检查
│
├── 敏感路径拒绝
│ ├── /etc, /usr, /bin, /sbin
│ ├── ~/.ssh, ~/.gnupg, ~/.aws
│ ├── /dev, /proc, /sys
│ └── Windows: C:\Windows, C:\Program Files
│
├── sed -i 特殊处理
│ ├── 解析 sed 表达式
│ ├── 提取目标文件路径
│ ├── 验证文件路径在工作目录内
│ └── 拒绝对系统文件的 in-place 编辑
│
└── 只读验证(readOnlyValidation.ts, 68.66KB)
├── Plan Mode 下所有写入操作被拒绝
├── 只读工作目录中的写入被拒绝
└── 只读 MCP 工具的写入被拒绝

沙箱模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
沙箱执行模型
│
├── macOS: sandbox-exec (seatbelt)
│ ├── 限制文件系统访问(只允许工作目录)
│ ├── 限制网络访问(可配置)
│ ├── 限制进程创建
│ └── 排除命令列表(某些命令不适合沙箱)
│
├── Linux: 容器隔离
│ ├── namespace 隔离
│ ├── cgroup 资源限制
│ └── seccomp 系统调用过滤
│
├── autoAllowBashIfSandboxed
│ ├── 沙箱启用时,Bash 命令自动放行
│ ├── 不适合沙箱的命令(排除列表)仍需确认
│ └── dangerouslyDisableSandbox 时不生效
│
└── Windows: 无沙箱
└── PowerShellTool 作为替代

权限模式

模式 说明 行为
default 默认模式 按规则匹配,未匹配则交互式确认
auto 自动模式 LLM 分类器判断,高置信度自动放行
bypassPermissions 绕过模式 所有工具自动放行(危险)
plan 计划模式 只允许只读工具,写入工具被拒绝

Plan Mode 的进入/退出:

  • 进入:EnterPlanModeTool 或模型自主决定
  • 退出:ExitPlanModeTool
  • 保存:prePlanMode 字段记录进入前的模式,退出时恢复

Workspace Trust

所有 Hook 执行都需要 workspace trust——因为 Hook 执行来自 .claude/settings.json 的任意命令。

历史安全漏洞:

  • SessionEnd hooks 在用户拒绝 trust dialog 时执行
  • SubagentStop hooks 在子代理完成前执行
  • 这些都已修复,现在所有 Hook 都严格检查 trust 状态

ToolPermissionContext 的不可变性

ToolPermissionContext 使用 DeepImmutable<> 类型包装,确保权限上下文在传递过程中不被意外修改:

1
2
3
4
5
6
7
8
9
10
11
12
export type ToolPermissionContext = DeepImmutable<{
mode: PermissionMode
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules: ToolPermissionRulesBySource
alwaysDenyRules: ToolPermissionRulesBySource
alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable: boolean
isAutoModeAvailable?: boolean
shouldAvoidPermissionPrompts?: boolean // 后台代理自动拒绝
awaitAutomatedChecksBeforeDialog?: boolean // 协调器等待自动化检查
prePlanMode?: PermissionMode // Plan Mode 前的模式
}>

权限更新通过 setAppState 的函数式更新实现——创建新的 context 对象,而非修改现有对象。这保证了并发安全——多个工具可能同时检查权限。


五、上下文管理与压缩(Compaction)

Claude Code 的上下文管理系统解决了 LLM 的核心限制——有限的上下文窗口。系统通过五层渐进式管线,在保留关键信息的同时最大化可用上下文空间。

五层管线总览

每次 API 调用前,消息列表经过以下五层管线处理(按执行顺序):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
原始消息 messages[]
│
├── Layer 1: applyToolResultBudget() // 大工具结果裁剪
│ ├── 计算所有工具结果的总 token 数
│ ├── 超过 toolResultBudget 的结果 → 持久化到磁盘
│ └── 替换为预览 + 文件路径引用
│
├── Layer 2: snipCompact() // 历史裁剪 [feature: HISTORY_SNIP]
│ ├── 从最旧的消息开始
│ ├── 替换为 "[message snipped]" 占位符
│ └── 保留最近 N 条消息不裁剪
│
├── Layer 3: microcompact() // 微压缩
│ ├── 合并连续的 FILE_UNCHANGED 结果
│ ├── 折叠重复的工具调用模式
│ └── 替换为 FILE_UNCHANGED_STUB
│
├── Layer 4: contextCollapse() // 上下文折叠 [feature: CONTEXT_COLLAPSE]
│ ├── 识别可折叠的上下文块
│ ├── 替换为摘要占位符
│ └── 保留关键的工具结果不折叠
│
└── Layer 5: autoCompact() // 自动压缩
├── 检查 token 使用率是否超过阈值
├── 超过 → 调用 LLM 生成摘要
└── 替换历史消息为压缩摘要

Layer 1: applyToolResultBudget

src/utils/messages/applyToolResultBudget.ts 实现了工具结果的预算裁剪。

触发条件:当所有工具结果的总 token 数超过 toolResultBudget 时触发。

裁剪策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
applyToolResultBudget 裁剪策略
│
├── 1. 计算每个工具结果的 token 数
│ └── 使用 countTokens() 精确计算
│
├── 2. 按 token 数降序排列
│
├── 3. 从最大的结果开始裁剪
│ ├── 将完整结果写入磁盘文件
│ ├── 替换为预览(前 N 行 + 后 N 行)
│ └── 附加文件路径引用
│
├── 4. 重复直到总 token 数 < toolResultBudget
│
└── 5. 特殊豁免
├── FileReadTool 结果(maxResultSizeChars = Infinity)
├── 最近一轮的工具结果(保留完整性)
└── 已经被裁剪过的结果(避免重复裁剪)

Layer 2: snipCompact

src/utils/messages/snipCompact.ts 实现了基于历史位置的裁剪。

核心逻辑:从最旧的消息开始,将消息内容替换为 [message snipped] 占位符。保留最近的消息不裁剪——这些消息包含当前任务的关键上下文。

1
2
3
// snipCompact 的关键参数
const SNIP_PRESERVE_RECENT = 4 // 保留最近 4 轮对话
const SNIP_MIN_TOKENS = 1000 // 消息 < 1000 tokens 不裁剪(开销不值得)

Feature Gate:HISTORY_SNIP 门控。未启用时跳过此层。

Layer 3: microcompact

src/utils/messages/microcompact.ts 实现了细粒度的冗余消除。

三种微压缩策略:

策略 触发条件 替换内容
FILE_UNCHANGED 连续多次 Read 同一文件且内容未变 FILE_UNCHANGED_STUB
TOOL_RESULT_MERGE 连续相同工具的相似结果 合并为单条
EMPTY_RESULT_STRIP 空的工具结果 移除
1
2
3
4
// FILE_UNCHANGED_STUB 的格式
const FILE_UNCHANGED_STUB = '<file_unchanged />'
// 当模型连续 Read 同一文件时,第二次及之后的结果被替换为此 stub
// 节省大量 token(一个大文件可能 10K+ tokens)

Layer 4: contextCollapse

src/utils/messages/contextCollapse.ts 实现了上下文块的折叠。

折叠策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
contextCollapse 折叠策略
│
├── 识别可折叠块
│ ├── 连续的工具调用 + 结果对
│ ├── 长的 assistant 消息
│ └── 旧的 user 消息
│
├── 折叠规则
│ ├── 保留最近 N 轮不折叠
│ ├── 保留包含错误的工具结果
│ ├── 保留包含文件修改的工具结果
│ └── 保留用户明确引用的内容
│
├── 折叠格式
│ ├── 工具调用 → "[Tool: {name}({summary})]"
│ ├── 工具结果 → "[Result: {brief_summary}]"
│ └── 长消息 → "[Message: {first_line}... (collapsed)]"
│
└── drain 机制
├── prompt-too-long 错误时触发
├── 逐步展开已折叠的块
└── 为 reactive compact 腾出空间

Feature Gate:CONTEXT_COLLAPSE 门控。

drain 机制是 contextCollapse 的关键特性——当 API 返回 prompt-too-long(413)错误时,系统不是立即触发昂贵的 LLM 压缩,而是先尝试"排水"已折叠的上下文块。这是一个零成本的恢复策略。

Layer 5: autoCompact

src/utils/autoCompact.ts 是最重量级的压缩层——调用 LLM 生成对话摘要。

触发条件:

1
2
3
4
5
6
7
8
9
10
11
12
// autoCompact 触发阈值
const AUTO_COMPACT_THRESHOLD = 0.8 // token 使用率 > 80%
const AUTO_COMPACT_TARGET = 0.5 // 压缩后目标使用率 50%

// 计算公式
const currentUsage = countTokens(messages) / contextWindowSize
if (currentUsage > AUTO_COMPACT_THRESHOLD) {
await compactConversation(messages, {
targetUsage: AUTO_COMPACT_TARGET,
...options
})
}

熔断器:

1
2
3
4
5
6
7
8
// 防止压缩循环的熔断器
const COMPACT_COOLDOWN = 3 // 压缩后至少 3 轮对话才能再次压缩
let turnsSinceLastCompact = 0

if (turnsSinceLastCompact < COMPACT_COOLDOWN) {
// 跳过 autoCompact,即使超过阈值
// 防止"压缩 → 新消息 → 又超阈值 → 再压缩"的死循环
}

compactConversation 完整流程

src/utils/compactConversation.ts 实现了 LLM 驱动的对话压缩:

压缩 Prompt 的关键指令:

1
2
3
4
5
6
7
8
9
10
11
12
你正在压缩一段对话历史。请生成一个简洁的摘要,保留以下关键信息:
1. 用户的原始请求和目标
2. 已完成的操作和结果
3. 当前的工作状态
4. 未完成的任务
5. 重要的文件路径和代码片段
6. 错误信息和解决方案

不要保留:
- 冗余的工具调用细节
- 重复的文件内容
- 中间的探索性尝试(除非结论重要)

手动压缩命令

用户可以通过 /compact 斜杠命令手动触发压缩:

1
2
3
4
5
6
7
8
/compact [instructions]
│
├── 无参数 → 使用默认压缩指令
├── 有参数 → 将参数作为额外的压缩指令
│ 例如:/compact 保留所有文件路径和错误信息
│
└── 执行 compactConversation()
└── 与 autoCompact 相同的流程

Token 计数与预算

src/utils/countTokens.ts 提供精确的 token 计数:

1
2
3
4
5
6
7
8
9
10
11
// Token 计数的层级
countTokens(messages) // 消息级别
countToolResultTokens(result) // 工具结果级别
estimateTokenCount(text) // 文本估算(快速,不精确)

// 预算分配
const TOKEN_BUDGET = {
systemPrompt: 0.15, // 系统提示占 15%
toolSchemas: 0.10, // 工具 schema 占 10%
conversation: 0.75, // 对话历史占 75%(可压缩)
}

normalizeMessages

src/utils/messages/normalizeMessages.ts 是管线的最后一步——将内部消息格式转换为 API 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
normalizeMessages 转换规则
│
├── 合并连续的同角色消息
│ └── API 要求 user/assistant 交替
│
├── 移除空消息
│ └── 压缩后可能产生空消息
│
├── 处理 cache_control
│ ├── 标记 ephemeral breakpoint
│ └── 用于 Prompt Cache 优化
│
├── 处理 tool_result 格式
│ ├── 内部格式 → API 格式
│ ├── 截断超长结果
│ └── 添加 is_error 标记
│
└── 注入 prependUserContext
├── 用户上下文(文件列表、git 状态等)
└── 附加到第一条 user 消息

Prompt-Too-Long 三级恢复

当 API 返回 413 prompt-too-long 错误时,系统按以下顺序尝试恢复:

SUMMARIZE_TOOL_RESULTS 模式

当 SUMMARIZE_TOOL_RESULTS feature flag 启用时,system prompt 中注入额外指令,要求模型在工具结果过长时自行生成摘要。这是一种"模型侧压缩"——不依赖后处理管线,而是让模型在生成响应时就进行信息压缩。

1
2
3
SUMMARIZE_TOOL_RESULTS 指令(注入 system prompt):
"当工具结果超过 2000 tokens 时,在你的响应中只引用关键信息,
不要逐字复述工具输出。用 <summary> 标签包裹你的摘要。"

这与五层管线互补——管线处理历史消息,SUMMARIZE_TOOL_RESULTS 处理当前轮次的新结果。

Session Memory 与压缩的协作

src/services/compact/sessionMemoryCompact.ts(21.18KB)实现了压缩过程中的会话记忆保留。当 autoCompact 触发时,大量对话细节被摘要替代,但某些信息对当前任务至关重要。Session Memory 在压缩前提取这些关键信息,压缩后重新注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Session Memory 与 Compact 的协作
│
├── 压缩前
│ ├── sessionMemoryCompact 分析即将被压缩的消息
│ ├── 提取关键上下文:
│ │ ├── 用户的原始请求和目标
│ │ ├── 当前工作状态(正在做什么、进度如何)
│ │ ├── 本次会话中用户提出的特殊约束
│ │ ├── 未完成的任务列表
│ │ └── 关键的文件路径和代码位置
│ └── 存储为 SessionMemory 对象
│
├── 压缩中
│ ├── compactConversation() 正常执行 LLM 摘要
│ └── 旧消息被替换为压缩摘要
│
└── 压缩后
├── Session Memory 作为独立消息注入
├── 位置:紧跟在压缩摘要之后
├── 格式:<session_memory> XML 标签
└── 确保模型在压缩后仍然知道关键上下文

这解决了一个核心矛盾:压缩需要丢弃细节以节省空间,但当前任务需要某些细节才能继续。Session Memory 是这个矛盾的折中方案——只保留对当前任务最关键的信息。

compact_boundary 标记

压缩后的摘要消息带有 compact_boundary 标记,用于标识压缩边界。这个标记有多个用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
compact_boundary 的作用
│
├── 1. 防止重复压缩
│ └── autoCompact 不会压缩 compact_boundary 之后的消息
│ (这些是压缩后新产生的消息,不应该被再次压缩)
│
├── 2. 消息分区
│ ├── compact_boundary 之前 = 压缩摘要(不可展开)
│ └── compact_boundary 之后 = 原始消息(可压缩)
│
├── 3. Token 计数基准
│ └── autoCompact 只计算 compact_boundary 之后的消息 token 数
│ (摘要部分的 token 数是固定的,不需要重复计算)
│
└── 4. Session Memory 注入点
└── Session Memory 注入在 compact_boundary 之后的第一条消息之前

Prompt Cache 断裂检测

src/services/api/promptCacheBreakDetection.ts(26.38KB)实现了 Prompt Cache 命中率的实时监控。当检测到 cache 命中率异常下降时,系统会尝试诊断原因并自动修复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Prompt Cache 断裂检测
│
├── 监控指标
│ ├── cache_creation_input_tokens(新创建的缓存 token 数)
│ ├── cache_read_input_tokens(从缓存读取的 token 数)
│ └── cache_hit_rate = read / (read + creation)
│
├── 断裂检测
│ ├── 连续 N 次 API 调用的 cache_hit_rate < 阈值
│ ├── 或 cache_creation_input_tokens 突然大幅增加
│ └── → 触发断裂告警
│
├── 常见断裂原因
│ ├── System Prompt 动态部分变化(记忆更新、MCP 工具变化)
│ ├── 工具 Schema 顺序变化(MCP 工具增减)
│ ├── 消息格式变化(normalizeMessages 的行为变化)
│ └── API 端侧缓存过期
│
└── 自动修复
├── 重新排序工具 Schema(恢复稳定顺序)
├── 冻结动态 Prompt 部分(延迟更新到下一轮)
└── 记录诊断信息到遥测

Token 估算算法

src/services/tokenEstimation.ts(16.97KB)提供了多种精度的 token 估算方法:

方法 精度 速度 适用场景
countTokens() 精确 慢(~1ms/消息) 压缩决策、预算检查
estimateTokenCount() 近似(±10%) 快(~0.01ms) 快速预检、UI 显示
estimateToolResultTokens() 近似 快 工具结果预算

estimateTokenCount() 使用简单的字符数/4 启发式——对英文文本误差约 ±10%,对代码误差约 ±15%。这个精度足够用于快速预检(如判断是否需要触发 autoCompact),但不够用于精确的预算计算。

精确计数的缓存:countTokens() 的结果会被缓存——对于未变化的消息,不需要重复计数。缓存 key 是消息内容的 hash。这在长会话中显著减少了 token 计数的开销——只有新增的消息需要计数,历史消息使用缓存值。

contentReplacementState 持久化

applyToolResultBudget 的替换记录通过 contentReplacementState 持久化到会话文件中。这支持了会话恢复——当用户恢复一个之前的会话时,系统可以重放替换记录,将磁盘文件引用正确地映射回原始工具结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
contentReplacementState 持久化规则
│
├── 持久化条件
│ ├── 只有 agent:* 和 repl_main_thread 来源的查询持久化
│ └── 临时的 runForkedAgent 调用不持久化
│
├── 持久化内容
│ ├── 替换映射(tool_use_id → 磁盘文件路径)
│ ├── 原始结果的 token 数
│ └── 替换时间戳
│
└── 恢复流程
├── 加载会话文件
├── 重建 contentReplacementState
└── 后续的 applyToolResultBudget 可以识别已替换的结果

六、记忆系统(Memory System)

Claude Code 的记忆系统实现了跨会话的知识持久化,是整个产品"越用越懂你"体验的核心。系统由五个子模块组成:CLAUDE.md 文件层级(项目约定)、memdir 结构化记忆(用户偏好与知识)、Session Memory 会话记忆(单次会话内的短期记忆)、团队记忆同步(跨设备/跨成员共享)、Auto Dream 记忆整固(后台记忆优化)。

核心文件

文件 大小 说明
src/utils/claudemd.ts 46.75 KB CLAUDE.md 加载、解析、@include 展开
src/memdir/memdir.ts 21.17 KB memdir 记忆目录核心操作
src/memdir/memoryTypes.ts 22.59 KB 记忆类型定义(schema、标签、元数据)
src/memdir/findRelevantMemories.ts 5.32 KB 相关记忆检索(语义匹配)
src/memdir/paths.ts 10.69 KB 记忆路径管理(项目哈希、目录结构)
src/memdir/teamMemPaths.ts 11.70 KB 团队记忆路径
src/memdir/teamMemPrompts.ts 5.96 KB 团队记忆 prompt 模板
src/services/extractMemories/extractMemories.ts 21.78 KB 自动记忆提取引擎
src/services/extractMemories/prompts.ts 7.64 KB 提取 prompt 模板
src/services/teamMemorySync/index.ts 44.34 KB 团队记忆同步服务
src/services/teamMemorySync/secretScanner.ts 9.55 KB 秘密扫描器(防泄漏)
src/services/autoDream/autoDream.ts 11.31 KB Auto Dream 记忆整固
src/services/SessionMemory/sessionMemory.ts 16.66 KB 会话记忆管理
src/services/SessionMemory/prompts.ts 12.65 KB 会话记忆 prompt
src/services/compact/sessionMemoryCompact.ts 21.18 KB 会话记忆压缩

CLAUDE.md 四级加载体系

记忆文件按作用域分为四级,从全局到局部逐级加载。src/utils/claudemd.ts(46.75KB)是这个体系的核心实现。

级别 路径 作用域 说明
1. 用户级 ~/.claude/CLAUDE.md 全局 用户个人偏好,跨所有项目生效
2. 项目根级 {projectRoot}/CLAUDE.md 项目 项目级约定,团队共享(checked in)
3. 子目录级 {cwd}/CLAUDE.md 目录 子模块/子目录特定指令
4. 父目录级 {parent}/.claude/CLAUDE.md 祖先 向上遍历直到文件系统根

加载顺序:用户级 → 父目录级(从根到当前目录)→ 项目根级 → 子目录级。后加载的内容优先级更高——子目录的 CLAUDE.md 可以覆盖项目根的指令。

claudemd.ts 的完整解析流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
loadClaudeMd(projectRoot, cwd, userHome)
│
├── 1. 发现阶段
│ ├── findClaudeMdFiles(userHome) // ~/.claude/CLAUDE.md
│ ├── findClaudeMdFiles(projectRoot) // {projectRoot}/CLAUDE.md
│ ├── findClaudeMdFiles(cwd) // {cwd}/CLAUDE.md(如果 cwd ≠ projectRoot)
│ ├── walkAncestors(projectRoot → /) // 向上遍历所有祖先目录
│ │ └── 每个目录检查 .claude/CLAUDE.md 和 CLAUDE.md
│ └── 去重(同一文件不加载两次)
│
├── 2. 读取阶段
│ ├── 对每个发现的文件:
│ │ ├── readFile(path, 'utf-8')
│ │ ├── 检查文件大小 ≤ MAX_CLAUDEMD_SIZE(100KB)
│ │ └── 超过大小限制 → 截断 + 警告
│ └── Trust 检查
│ ├── 用户级文件 → 始终信任
│ ├── 项目级文件 → 需要 workspace trust
│ └── 未信任 → 跳过(不加载)
│
├── 3. @include 展开阶段
│ ├── 正则匹配 /^@(.+)$/gm
│ ├── 对每个 @include:
│ │ ├── 解析路径(相对于当前 CLAUDE.md 所在目录)
│ │ ├── 安全检查
│ │ │ ├── 路径必须在工作目录内
│ │ │ ├── 不能是符号链接指向外部
│ │ │ └── 不能包含 .. 逃逸
│ │ ├── 递归深度检查(最大 5 层)
│ │ ├── 读取文件内容
│ │ └── 递归展开嵌套的 @include
│ └── 文件不存在 → 静默忽略(不报错,不中断)
│
├── 4. 格式化阶段
│ ├── 每个来源用 XML 标签包裹
│ │ ├── <user_instructions source="~/.claude/CLAUDE.md">
│ │ ├── <project_instructions source="{projectRoot}/CLAUDE.md">
│ │ └── <directory_instructions source="{cwd}/CLAUDE.md">
│ ├── 标签中包含来源路径(便于模型引用)
│ └── 拼接为最终字符串
│
└── 5. Token 预算检查
├── 计算总 token 数
├── 超过预算 → 按优先级裁剪
└── 返回最终的记忆 prompt

@include 的实际使用场景:

1
2
3
4
5
6
7
8
9
# 项目 CLAUDE.md 示例
@docs/coding-standards.md # 引用编码规范文档
@.claude/prompts/review-checklist.md # 引用代码审查清单
@scripts/README.md # 引用脚本说明

## 项目特定规则
- 使用 TypeScript strict mode
- 所有 API 端点必须有 OpenAPI 注释
- 提交消息遵循 Conventional Commits

CLAUDE.md 的安全模型:

项目级 CLAUDE.md 是一个潜在的攻击向量——恶意仓库可以在 CLAUDE.md 中注入指令,让模型执行危险操作。Claude Code 通过以下机制防御:

  1. Workspace Trust:项目级 CLAUDE.md 只在用户确认信任工作区后才加载。首次打开项目时,Trust Dialog 会显示 CLAUDE.md 的内容预览。
  2. @include 沙箱:@include 只能引用工作目录内的文件,不能逃逸到系统目录。
  3. 大小限制:单个文件 100KB,防止通过超大文件消耗上下文窗口。
  4. InstructionsLoaded Hook:企业可以通过 Hook 审计加载的指令内容。

Memdir 结构化记忆

src/memdir/memdir.ts(21.17KB)实现了结构化的记忆存储系统。与 CLAUDE.md 的纯文本记忆不同,memdir 提供了类型化、可检索、可同步的记忆条目。

目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/.claude/memory/
├── user/ // 用户级记忆(全局)
│ ├── preferences.md // 用户偏好
│ ├── coding-style.md // 编码风格
│ └── tools.md // 工具使用习惯
│
├── project-{hash}/ // 项目级记忆(按项目哈希隔离)
│ ├── architecture.md // 架构决策
│ ├── conventions.md // 项目约定
│ ├── common-errors.md // 常见错误与解决方案
│ └── dependencies.md // 依赖选择理由
│
├── team/ // 团队记忆(跨成员共享)
│ ├── shared-conventions.md // 团队共享约定
│ └── onboarding.md // 新成员引导
│
└── .index.json // 记忆索引(元数据缓存)

项目哈希计算:src/memdir/paths.ts 中的 getProjectHash() 使用项目根目录的绝对路径计算 SHA-256 哈希的前 12 位。这保证了不同项目的记忆完全隔离,即使项目名称相同。

记忆类型系统:

src/memdir/memoryTypes.ts(22.59KB)定义了记忆条目的完整类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface MemoryEntry {
// 元数据
id: string // 唯一标识符(UUID)
type: MemoryType // 记忆类型
scope: 'user' | 'project' | 'team' // 作用域

// 内容
title: string // 标题(用于检索)
content: string // 正文(Markdown 格式)
tags: string[] // 标签(用于分类和检索)

// 时间戳
createdAt: string // 创建时间(ISO 8601)
updatedAt: string // 最后更新时间
lastAccessedAt: string // 最后访问时间(用于 LRU 淘汰)

// 来源追踪
source: 'auto' | 'manual' | 'dream' | 'team-sync' // 创建来源
sessionId?: string // 创建时的会话 ID
confidence: number // 置信度(0-1,auto 提取的记忆通常 < 1)
}

type MemoryType =
| 'preference' // 用户偏好("我喜欢用 tabs")
| 'convention' // 项目约定("使用 camelCase")
| 'architecture' // 架构决策("使用微服务架构")
| 'error-solution' // 错误解决方案
| 'workflow' // 工作流程
| 'context' // 上下文信息(项目背景、团队结构等)

记忆的 CRUD 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
memdir CRUD 操作
│
├── createMemory(entry)
│ ├── 生成 UUID
│ ├── 写入 Markdown 文件(frontmatter + content)
│ ├── 更新 .index.json
│ └── 触发团队同步(如果 scope === 'team')
│
├── readMemory(id)
│ ├── 从 .index.json 查找路径
│ ├── 读取文件内容
│ ├── 解析 frontmatter
│ └── 更新 lastAccessedAt
│
├── updateMemory(id, updates)
│ ├── 读取现有内容
│ ├── 合并更新
│ ├── 写入文件
│ ├── 更新 .index.json
│ └── 触发团队同步
│
├── deleteMemory(id)
│ ├── 删除文件
│ ├── 从 .index.json 移除
│ └── 触发团队同步(标记为已删除)
│
└── listMemories(filter?)
├── 读取 .index.json
├── 按 filter 过滤(scope、type、tags)
└── 按 updatedAt 降序排列

findRelevantMemories 相关性检索

src/memdir/findRelevantMemories.ts(5.32KB)实现了基于当前对话上下文的记忆检索——不是加载所有记忆,而是只加载与当前任务相关的记忆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
findRelevantMemories(context) 检索流程
│
├── 1. 构建查询向量
│ ├── 提取当前对话的关键词
│ ├── 提取最近工具调用涉及的文件路径
│ ├── 提取用户最近的问题/指令
│ └── 组合为查询字符串
│
├── 2. 候选记忆收集
│ ├── 加载 user/ 目录下所有记忆
│ ├── 加载 project-{hash}/ 目录下所有记忆
│ └── 加载 team/ 目录下所有记忆
│
├── 3. 相关性评分
│ ├── 标签匹配得分(tags 与查询关键词的交集)
│ ├── 标题匹配得分(title 与查询的模糊匹配)
│ ├── 内容匹配得分(content 中关键词出现频率)
│ ├── 时间衰减因子(最近访问的记忆得分更高)
│ └── 来源权重(manual > auto > dream)
│
├── 4. 排序与截断
│ ├── 按综合得分降序排列
│ ├── 取 top-K 条记忆(K 由 token 预算决定)
│ └── 确保总 token 数不超过 MEMORY_TOKEN_BUDGET
│
└── 5. 返回
└── 格式化为 <relevant_memories> XML 标签

与 Memory Prefetch 的集成:

在代理循环中,findRelevantMemories() 通过 prefetch 机制异步执行——在工具执行期间后台检索相关记忆,不阻塞主循环。检索结果作为附件消息注入到下一次 API 调用中:

1
2
3
4
5
6
7
8
9
10
11
12
13
// query.ts 中的 Memory Prefetch
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
state.messages, state.toolUseContext
)

// 工具执行完成后,消费 prefetch 结果
if (pendingMemoryPrefetch?.settledAt !== null && consumedOnIteration === -1) {
const memoryAttachments = filterDuplicateMemoryAttachments(
await pendingMemoryPrefetch.promise,
toolUseContext.readFileState // 过滤模型已 Read/Write/Edit 的文件
)
// memoryAttachments 作为 user 消息注入
}

去重逻辑:filterDuplicateMemoryAttachments() 过滤掉模型已经通过 Read/Write/Edit 工具访问过的文件——如果模型刚刚读取了 CLAUDE.md,就不需要再通过记忆系统注入相同的内容。


Auto Memory 自动记忆提取

src/services/extractMemories/extractMemories.ts(21.78KB)实现了自动记忆提取——在会话结束时,LLM 分析对话历史,提取值得跨会话记住的信息。

触发条件:

1
2
3
4
5
6
7
8
Auto Memory 触发条件(全部满足才触发)
│
├── 会话结束时(用户退出、/exit、或 headless 模式完成)
├── CLAUDE_CODE_SIMPLE !== '1'(bare 模式禁用)
├── feature('AUTO_MEMORY') 启用
├── 对话长度 > MIN_CONVERSATION_LENGTH(避免短对话触发)
├── 距离上次提取 > EXTRACT_COOLDOWN(防止频繁提取)
└── 用户未禁用自动记忆(settings.json 中的 autoMemory 配置)

提取流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
extractMemories(conversationHistory)
│
├── 1. 对话预处理
│ ├── 过滤系统消息和工具调用细节
│ ├── 保留用户消息和模型的关键响应
│ ├── 截断到最大 token 限制(避免超长对话的提取成本过高)
│ └── 标记用户明确表达偏好的消息("我喜欢..."、"请总是..."、"不要...")
│
├── 2. LLM 提取调用
│ ├── 使用 Haiku 模型(成本低、速度快)
│ ├── 注入提取 prompt(src/services/extractMemories/prompts.ts)
│ └── 要求返回结构化 JSON
│
├── 3. 提取 Prompt 的关键指令
│ ├── "分析对话历史,提取值得跨会话记住的信息"
│ ├── 关注类别:
│ │ ├── 用户明确表达的偏好("我喜欢..."、"请总是..."、"不要...")
│ │ ├── 项目特定的约定(命名规范、目录结构、依赖选择)
│ │ ├── 反复出现的工作模式(用户总是先写测试、总是要求代码审查)
│ │ ├── 重要的错误及其解决方案(特别是环境特定的问题)
│ │ └── 架构决策及其理由
│ ├── 不要提取:
│ │ ├── 临时性的任务细节("修复这个 bug")
│ │ ├── 具体的代码片段(除非是通用模板)
│ │ ├── 一次性的操作步骤
│ │ └── 已经在 CLAUDE.md 中记录的内容
│ └── 输出格式:JSON 数组,每条包含 type、title、content、tags、confidence
│
├── 4. 去重与冲突检测
│ ├── 加载现有记忆的 .index.json
│ ├── 对每条候选记忆:
│ │ ├── 标题相似度检查(Levenshtein 距离 < 阈值 → 视为重复)
│ │ ├── 内容相似度检查(关键词重叠率 > 阈值 → 视为重复)
│ │ ├── 标签完全匹配 → 可能是同一主题的更新
│ │ └── 冲突检测(新记忆与旧记忆矛盾 → 标记为更新)
│ ├── 重复 → 跳过
│ ├── 更新 → 合并到现有记忆
│ └── 新信息 → 创建新记忆条目
│
└── 5. 写入
├── 调用 createMemory() 或 updateMemory()
├── source 标记为 'auto'
├── confidence 使用 LLM 返回的置信度
└── 后台执行,不阻塞用户退出

Session Memory 会话记忆

src/services/SessionMemory/sessionMemory.ts(16.66KB)实现了单次会话内的短期记忆——与 memdir 的跨会话长期记忆不同,Session Memory 只在当前会话内有效,用于在压缩(compact)后保留关键的工作上下文。

Session Memory 的核心问题:当 autoCompact 压缩对话历史时,大量细节被摘要替代。但某些信息对当前任务至关重要——例如"用户要求所有函数都加 JSDoc"、“当前正在重构 auth 模块”。Session Memory 在压缩前提取这些关键信息,压缩后重新注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Session Memory 生命周期
│
├── 1. 提取(压缩前)
│ ├── sessionMemoryCompact.ts 在 compactConversation() 中调用
│ ├── 分析即将被压缩的消息
│ ├── 提取关键上下文:
│ │ ├── 用户的原始请求和目标
│ │ ├── 当前的工作状态(正在做什么、做到哪里了)
│ │ ├── 重要的约束和偏好(本次会话中用户提出的)
│ │ ├── 未完成的任务列表
│ │ └── 关键的文件路径和代码位置
│ └── 存储为 SessionMemory 对象
│
├── 2. 注入(压缩后)
│ ├── 在压缩摘要之后注入 Session Memory
│ ├── 格式:<session_memory> XML 标签
│ ├── 位置:紧跟在压缩摘要消息之后
│ └── 确保模型在压缩后仍然知道关键上下文
│
├── 3. 更新(每次压缩时)
│ ├── 新的压缩 → 新的 Session Memory 提取
│ ├── 与旧的 Session Memory 合并
│ ├── 去除已完成的任务
│ └── 更新工作状态
│
└── 4. 过期
└── 会话结束时自动丢弃(不持久化到 memdir)

Session Memory Prompt(src/services/SessionMemory/prompts.ts,12.65KB):

1
2
3
4
5
6
7
8
9
10
11
12
提取 Session Memory 的 prompt 指令:
"你正在分析一段即将被压缩的对话历史。请提取以下关键信息,
这些信息将在压缩后注入,帮助你继续工作:

1. 用户的核心目标(一句话总结)
2. 当前工作状态(正在做什么、进度如何)
3. 重要的约束(用户在本次会话中提出的特殊要求)
4. 未完成的任务(列表形式)
5. 关键的文件路径(正在编辑或需要关注的文件)
6. 重要的错误和解决方案(如果有)

格式要求:简洁、结构化、可直接作为上下文使用。"

团队记忆同步

src/services/teamMemorySync/index.ts(44.34KB)实现了跨设备、跨团队成员的记忆同步。

同步架构:

同步流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
teamMemorySync() 完整流程
│
├── 1. 触发条件
│ ├── 会话开始时(拉取远程变更)
│ ├── 记忆变更时(推送本地变更)
│ ├── 定期轮询(每 5 分钟检查远程变更)
│ └── 手动触发(/memory sync 命令)
│
├── 2. 拉取(Pull)
│ ├── GET /v1/memory-sync?since={lastSyncTimestamp}
│ ├── 获取自上次同步以来的远程变更
│ ├── 对每条远程变更:
│ │ ├── 本地不存在 → 创建
│ │ ├── 本地存在且远程更新 → 合并
│ │ ├── 本地已删除 → 跳过
│ │ └── 冲突(双方都修改) → 使用 LWW(Last Writer Wins)策略
│ └── 更新 lastSyncTimestamp
│
├── 3. 推送(Push)
│ ├── 收集自上次同步以来的本地变更
│ ├── 对每条变更执行 Secret Scanner
│ │ └── 检测到秘密 → 阻止推送 + 警告用户
│ ├── POST /v1/memory-sync
│ │ ├── 发送变更集(创建/更新/删除)
│ │ └── 附带本地时间戳
│ └── 更新 lastSyncTimestamp
│
└── 4. 冲突解决
├── LWW(Last Writer Wins):时间戳更新的版本胜出
├── 如果时间戳相同 → 内容更长的版本胜出
└── 删除操作优先级最低(防止误删)

Secret Scanner 秘密扫描器

src/services/teamMemorySync/secretScanner.ts(9.55KB)在记忆同步前扫描内容,防止敏感信息泄漏到远程存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Secret Scanner 检测规则
│
├── API Key 模式
│ ├── AWS: AKIA[0-9A-Z]{16}
│ ├── GitHub: ghp_[a-zA-Z0-9]{36}
│ ├── Anthropic: sk-ant-[a-zA-Z0-9-]+
│ ├── OpenAI: sk-[a-zA-Z0-9]{48}
│ └── 通用: [a-zA-Z0-9]{32,} 在 key/token/secret 上下文中
│
├── 私钥模式
│ ├── -----BEGIN RSA PRIVATE KEY-----
│ ├── -----BEGIN EC PRIVATE KEY-----
│ └── -----BEGIN OPENSSH PRIVATE KEY-----
│
├── 连接字符串模式
│ ├── postgresql://user:password@host
│ ├── mongodb://user:password@host
│ └── redis://user:password@host
│
├── 环境变量模式
│ ├── DATABASE_URL=...
│ ├── SECRET_KEY=...
│ └── PASSWORD=...
│
└── 处理策略
├── 检测到秘密 → 阻止同步
├── 记录警告日志
├── 通知用户("记忆中包含敏感信息,已阻止同步")
└── 建议用户移除敏感信息后重试

Auto Dream 记忆整固

src/services/autoDream/autoDream.ts(11.31KB)实现了一个后台"做梦"机制——在空闲时间运行一个子代理,对现有记忆进行整理、合并、优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Auto Dream 流程
│
├── 触发条件
│ ├── 会话空闲超过阈值(用户长时间未输入)
│ ├── 或会话结束后的后台任务
│ ├── 且 memdir 中记忆条目数 > MIN_MEMORIES_FOR_DREAM
│ └── 且距离上次 Dream > DREAM_COOLDOWN
│
├── Dream 子代理
│ ├── 使用 Haiku 模型(低成本)
│ ├── 加载所有现有记忆
│ ├── 分析记忆质量和组织结构
│ └── 执行以下操作:
│
├── 整固操作
│ ├── 合并重复记忆
│ │ └── 多条关于同一主题的记忆 → 合并为一条更全面的
│ ├── 更新过时记忆
│ │ └── 与最近的对话历史对比,更新不再准确的信息
│ ├── 提升记忆质量
│ │ └── 模糊的记忆 → 添加更多细节和上下文
│ ├── 调整标签
│ │ └── 统一标签命名,添加缺失的标签
│ └── 淘汰低价值记忆
│ └── 长时间未访问 + 低置信度 → 标记为候选删除
│
└── 结果
├── 更新后的记忆写回 memdir
├── source 标记为 'dream'
└── 记录 Dream 日志(用于审计)

loadMemoryPrompt 的完整组装

src/utils/memory.ts 中的 loadMemoryPrompt() 将所有记忆源组装为 system prompt 的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
loadMemoryPrompt() 组装顺序
│
├── 1. CLAUDE.md 层级
│ ├── 用户级 ~/.claude/CLAUDE.md(含 @include 展开)
│ ├── 父目录链 CLAUDE.md(从根到当前目录)
│ ├── 项目根级 {projectRoot}/CLAUDE.md(含 @include 展开)
│ └── 子目录级 {cwd}/CLAUDE.md(如果 cwd ≠ projectRoot)
│
├── 2. Memdir 结构化记忆
│ ├── user/ 目录下的相关记忆(findRelevantMemories 筛选)
│ ├── project-{hash}/ 目录下的相关记忆
│ └── team/ 目录下的相关记忆
│
├── 3. Session Memory(如果存在)
│ └── 上一次压缩后保留的会话记忆
│
├── 4. 格式化输出
│ ├── <user_instructions>...</user_instructions>
│ ├── <project_instructions>...</project_instructions>
│ ├── <relevant_memories>...</relevant_memories>
│ └── <session_memory>...</session_memory>
│
└── 5. Token 预算裁剪
├── 总预算:~16K tokens
├── 超过预算时按优先级裁剪:
│ ├── 结构化记忆(最旧的条目先裁剪)
│ ├── 父目录链记忆(最远的祖先先裁剪)
│ ├── 项目级记忆(尾部裁剪)
│ └── 用户级记忆(尾部裁剪)
└── CLAUDE.md 内容优先级高于 memdir 记忆

记忆的 Token 预算

记忆内容有严格的 token 预算限制,防止过大的记忆文件占满上下文窗口:

1
2
3
4
5
6
7
const MEMORY_TOKEN_BUDGET = {
userInstructions: 4000, // 用户级 CLAUDE.md 最大 4K tokens
projectInstructions: 8000, // 项目级 CLAUDE.md 最大 8K tokens
relevantMemories: 4000, // 相关记忆最大 4K tokens
sessionMemory: 2000, // 会话记忆最大 2K tokens
total: 16000, // 总计最大 16K tokens
}

预算分配策略:CLAUDE.md 的预算是固定的(用户和项目各自有上限),memdir 记忆的预算是弹性的——如果 CLAUDE.md 使用的 token 较少,剩余的预算会分配给 memdir 记忆。


/memory 命令

用户可以通过 /memory 斜杠命令管理记忆:

1
2
3
4
5
6
7
8
9
10
11
/memory                        → 显示当前加载的所有记忆来源和 token 使用情况
/memory edit → 打开编辑器编辑项目级 CLAUDE.md
/memory edit --global → 编辑用户级 ~/.claude/CLAUDE.md
/memory edit --project → 编辑项目级 CLAUDE.md
/memory list → 列出 memdir 中的所有记忆条目
/memory list --project → 只列出项目级记忆
/memory list --tags typescript → 按标签过滤
/memory clear → 清除所有结构化记忆
/memory clear --project → 只清除项目级结构化记忆
/memory sync → 手动触发团队记忆同步
/memory dream → 手动触发 Auto Dream 整固

InstructionsLoaded Hook

当 CLAUDE.md 加载完成后,触发 InstructionsLoaded Hook,允许企业审计和控制记忆内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Hook 输入
{
event: 'InstructionsLoaded',
data: {
sources: [
{ path: '~/.claude/CLAUDE.md', content: '...', tokens: 1200 },
{ path: '/project/CLAUDE.md', content: '...', tokens: 2300 },
],
totalTokens: 3500,
memoryEntries: 12, // memdir 中加载的记忆条目数
teamMemories: 3, // 团队记忆条目数
}
}

// Hook 可以返回
{
suppressOutput: true, // 阻止某些记忆加载
systemMessage: '...', // 注入额外的系统消息
}

记忆与 Prompt Cache 的交互

记忆内容位于 system prompt 的动态区域(SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之后),这意味着记忆内容的变化不会破坏静态区域的 Prompt Cache。但记忆内容本身的变化会导致动态区域的 cache miss。

优化策略:

  • 会话内缓存:记忆内容在会话内缓存,只在 settingsChangeDetector 检测到 CLAUDE.md 文件变化时重新加载
  • @include 缓存:@include 文件的内容通过文件 mtime 检测变化,未变化时使用缓存
  • memdir 索引缓存:.index.json 的 hash 用于快速检测记忆变化,避免每次都遍历目录
  • Memory Prefetch 去重:filterDuplicateMemoryAttachments() 避免注入模型已经通过工具访问过的文件内容
  • 稳定排序:记忆条目按 ID 排序注入,保证相同的记忆集合产生相同的 prompt 文本,最大化 cache 命中率

记忆注入策略:System Prompt vs Tool Result

记忆有两种注入方式,适用于不同场景:

注入方式 时机 内容 适用场景
System Prompt 每次 API 调用 CLAUDE.md + 高优先级记忆 始终需要的项目约定和用户偏好
Tool Result 工具执行后的附件 相关记忆(findRelevantMemories) 与当前任务相关的上下文记忆

System Prompt 注入的记忆是"始终可见"的——模型在每次响应中都能看到。Tool Result 注入的记忆是"按需可见"的——只在相关时才出现,节省上下文空间。

这种双轨策略平衡了记忆覆盖率和上下文效率:核心约定通过 System Prompt 始终生效,长尾知识通过 Tool Result 按需注入。


七、多代理架构(Multi-Agent)

Claude Code 支持多种代理生成模式,从简单的子代理(AgentTool)到完整的团队协作(Swarm),实现了从单任务委派到多代理并行协作的完整谱系。

四种代理生成模式

模式 进程模型 上下文共享 通信方式 适用场景
AgentTool 同进程 共享 prompt cache 函数调用 简单子任务委派
Fork 子进程 继承父上下文 IPC 需要隔离的并行任务
Swarm 独立进程 无共享 消息队列 多代理协作
Task 后台进程 无共享 轮询/通知 长时间异步任务

AgentTool 子代理

src/tools/AgentTool/AgentTool.ts 是最常用的子代理模式。主代理通过 AgentTool 工具调用创建子代理,子代理在同一进程内执行。

子代理的 Prompt 构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AgentTool 子代理 Prompt
│
├── System Prompt
│ ├── 继承主代理的 system prompt(静态部分)
│ ├── 添加子代理特定指令
│ │ ├── "你是一个子代理,专注于完成特定任务"
│ │ ├── "完成后使用 TaskOutput 返回结果"
│ │ └── "不要偏离分配的任务"
│ └── 工具集限制(可能比主代理少)
│
├── 初始消息
│ ├── 主代理的任务描述
│ ├── 相关上下文(文件路径、代码片段)
│ └── 期望的输出格式
│
└── 约束
├── maxTurns: 受限(防止无限循环)
├── 权限: 继承主代理的权限模式
└── 工具: 可配置的工具子集

Prompt Cache 共享:AgentTool 子代理与主代理共享 system prompt 的静态部分,这意味着子代理的首次 API 调用可以命中主代理的 Prompt Cache——节省约 15% 的 token 成本。

递归防护:

1
2
3
4
5
6
7
8
9
10
// 子代理递归深度限制
const MAX_AGENT_DEPTH = 3 // 最大嵌套深度

// AgentTool 内部检查
if (currentDepth >= MAX_AGENT_DEPTH) {
return {
type: 'error',
error: `Maximum agent nesting depth (${MAX_AGENT_DEPTH}) exceeded`
}
}

内置代理类型

通过 --agents 参数或 .claude/agents/ 目录定义的代理模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
代理定义格式(.claude/agents/*.md 或 .claude/agents/*.yml)
│
├── Markdown 格式
│ ├── 文件名 = 代理名称
│ ├── 文件内容 = 代理的 system prompt 补充
│ └── 通过 @include 引用其他文件
│
└── YAML 格式
├── name: 代理名称
├── description: 代理描述
├── prompt: 代理的 system prompt 补充
├── tools: 可用工具列表
├── maxTurns: 最大迭代次数
└── model: 使用的模型(可覆盖)

代理发现:getAgentDefinitionsWithOverrides() 在启动时扫描 .claude/agents/ 目录,将每个文件解析为代理定义。用户可以通过 @agent-name 在对话中引用特定代理。

Fork 子代理

Fork 模式通过 child_process.fork() 创建独立的 Node.js 进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Fork 子代理生命周期
│
├── 创建
│ ├── fork() 创建子进程
│ ├── 传递父代理的上下文快照
│ │ ├── 当前消息历史
│ │ ├── 工作目录
│ │ ├── 环境变量
│ │ └── 权限配置
│ └── 子进程独立运行 query() 循环
│
├── 通信
│ ├── IPC channel(process.send / process.on('message'))
│ ├── 进度报告(子 → 父)
│ ├── 权限请求(子 → 父 → 用户 → 父 → 子)
│ └── 结果返回(子 → 父)
│
├── 隔离
│ ├── 独立的内存空间
│ ├── 独立的文件句柄
│ ├── 独立的 API 连接
│ └── 崩溃不影响父进程
│
└── 终止
├── 正常完成 → 返回结果
├── 超时 → SIGTERM → SIGKILL
└── 父进程退出 → 子进程自动终止

Swarm 团队协作

src/utils/swarm/ 实现了多代理团队协作模式(feature: AGENT_SWARMS)。

团队创建:

SendMessageTool:团队成员之间通过 SendMessageTool 通信。消息通过共享的消息队列传递,每个代理在自己的循环中轮询新消息。

TeamDeleteTool:销毁团队,终止所有成员进程,清理资源。

协调器模式:当 awaitAutomatedChecksBeforeDialog 为 true 时,权限请求先经过协调器的自动化检查(classifier + hooks),只有未通过的请求才提交给用户。这减少了多代理场景下的权限提示频率。

Task 后台任务

Task 系统(feature: TASKS)支持长时间运行的后台任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Task 生命周期
│
├── TaskCreateTool
│ ├── 创建任务定义
│ ├── 分配唯一 ID
│ └── 启动后台进程
│
├── 执行中
│ ├── 独立的 query() 循环
│ ├── 定期写入进度到 TaskOutput
│ ├── 可通过 TaskGetTool 查询状态
│ └── 可通过 TaskStopTool 停止
│
├── 状态
│ ├── pending → 等待执行
│ ├── running → 执行中
│ ├── completed → 已完成
│ ├── failed → 失败
│ └── stopped → 被停止
│
└── 结果
├── TaskOutputTool 写入最终结果
├── TaskGetTool 读取结果
└── TaskListTool 列出所有任务

Todo V2:当 isTodoV2Enabled() 为 true 时,Task 系统升级为 Todo V2——支持任务依赖、优先级、标签等更丰富的任务管理功能。

后台会话管理

src/entrypoints/cli.tsx 中的 ps、logs、attach、kill 命令(feature: BG_SESSIONS)管理后台运行的代理会话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
后台会话命令
│
├── claude ps → 列出所有后台会话
│ └── 显示 ID、状态、运行时间、任务描述
│
├── claude logs <id> → 查看会话日志
│ └── 实时流式输出
│
├── claude attach <id> → 附加到后台会话
│ └── 恢复交互式控制
│
├── claude kill <id> → 终止后台会话
│ └── 发送 SIGTERM → 等待 → SIGKILL
│
└── --bg / --background → 以后台模式启动
└── 立即返回会话 ID

SubagentStop Hook

子代理完成时触发 SubagentStop Hook,允许父代理或 Hook 系统检查子代理的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SubagentStop Hook 输入
{
event: 'SubagentStop',
data: {
agentId: string,
agentType: 'agent' | 'fork' | 'swarm_member' | 'task',
result: ToolResult,
turnCount: number,
stopReason: string,
}
}

// Hook 可以返回
{
blocking: string[], // 阻塞错误 → 子代理继续执行
preventContinuation: boolean, // 阻止子代理继续
}

Worktree 模式

Worktree 模式(feature: WORKTREE_MODE)允许代理在 Git worktree 中工作,实现真正的文件系统隔离:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Worktree 模式
│
├── EnterWorktreeTool
│ ├── git worktree add <path> <branch>
│ ├── 切换工作目录到 worktree
│ └── 所有后续文件操作在 worktree 中
│
├── 工作流程
│ ├── 主代理在主工作目录
│ ├── 子代理在 worktree 中
│ ├── 互不干扰的文件修改
│ └── 通过 git merge 合并结果
│
└── ExitWorktreeTool
├── 切换回主工作目录
├── 可选:合并 worktree 的修改
└── 可选:删除 worktree

Tmux 集成:--worktree --tmux 快速路径在 cli.tsx 中处理——创建 worktree 并在新的 tmux 窗口中启动 Claude Code,实现真正的并行开发体验。

内置代理类型

源码中定义了多种内置代理类型(src/tools/AgentTool/built-in/),每种针对特定任务优化:

代理类型 用途 工具集 特殊行为
explore 代码探索和理解 Read, Glob, Grep, Bash(只读) 只读模式,不修改文件
plan 任务规划和分析 Read, Glob, Grep 生成结构化计划,不执行
verification 代码验证和测试 Read, Bash, Grep 运行测试、lint、类型检查
general-purpose 通用子任务 继承父代理工具集 默认类型,最灵活
claude-code-guide Claude Code 使用指导 Read 回答关于 Claude Code 本身的问题

每种内置代理都有专门的 system prompt 补充,指导模型在特定角色下的行为。例如 verification 代理的 prompt 强调"运行所有相关测试"、“检查类型错误”、“不要修复问题,只报告发现”。explore 代理的 prompt 强调"广泛搜索"、“理解代码结构”、“不要修改任何文件”。

代理选择逻辑:主代理在调用 AgentTool 时,通过 agentType 参数指定子代理类型。如果未指定,默认使用 general-purpose。模型会根据任务性质自主选择合适的代理类型——例如用户要求"帮我理解这个模块的架构"时,模型倾向于选择 explore 类型。

协调器模式(Coordinator Mode)

src/coordinator/coordinatorMode.ts(18.93KB)实现了协调器模式——一种更高级的多代理编排方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
协调器模式工作流程
│
├── 角色
│ ├── Coordinator(协调器):主代理,负责任务分配和结果汇总
│ └── Workers(工作者):子代理,负责执行具体任务
│
├── 工作流程
│ ├── 1. 用户提交任务给 Coordinator
│ ├── 2. Coordinator 分析任务,拆分为子任务
│ ├── 3. Coordinator 为每个子任务创建 Worker
│ │ ├── 选择合适的代理类型
│ │ ├── 分配工具权限
│ │ └── 提供任务描述和上下文
│ ├── 4. Workers 并行执行子任务
│ │ ├── 通过 SendMessageTool 向 Coordinator 报告进度
│ │ └── 完成后返回结果
│ ├── 5. Coordinator 汇总所有 Worker 结果
│ └── 6. Coordinator 生成最终响应给用户
│
├── Idle Cycle(空闲循环)
│ ├── Coordinator 在等待 Workers 时进入空闲循环
│ ├── 定期检查 Workers 的状态
│ ├── 处理 Workers 的权限请求
│ └── 响应用户的中断信号
│
├── Auto-Claim(自动认领)
│ ├── 当新任务到达时,Coordinator 自动认领
│ ├── 无需用户手动分配
│ └── 基于任务优先级和 Worker 可用性决策
│
└── 权限代理
├── awaitAutomatedChecksBeforeDialog = true
├── Workers 的权限请求先经过 Coordinator 的自动化检查
│ ├── classifier 判断
│ └── hooks 检查
└── 只有未通过自动化检查的请求才提交给用户

远程代理任务

src/tasks/RemoteAgentTask/RemoteAgentTask.tsx(124.26KB)和 src/tasks/LocalAgentTask/LocalAgentTask.tsx(81.63KB)实现了远程和本地代理任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
代理任务类型
│
├── LocalAgentTask(本地代理任务)
│ ├── 在本地进程中执行
│ ├── 共享本地文件系统
│ ├── 可以使用所有本地工具
│ └── 适用于需要文件系统访问的任务
│
├── RemoteAgentTask(远程代理任务)
│ ├── 在远程环境中执行(BYOC、Self-Hosted Runner)
│ ├── 通过 API 通信
│ ├── 独立的文件系统和环境
│ ├── 适用于 CI/CD 集成和云端执行
│ └── 支持 GitHub Action 集成
│
└── InProcessTeammateTask(进程内队友任务)
├── 在同一进程中执行
├── 共享内存空间
├── 最低开销
└── 适用于轻量级协作

Swarm 模式详细

Swarm 模式(src/utils/swarm/)实现了 Lead Agent + Teammates 的协作模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Swarm 模式架构
│
├── Lead Agent(领导代理)
│ ├── 接收用户任务
│ ├── 分析任务并创建团队
│ ├── 分配角色和职责
│ ├── 监控团队进度
│ └── 汇总最终结果
│
├── Teammates(队友代理)
│ ├── 接收 Lead Agent 分配的子任务
│ ├── 独立执行(独立进程、独立上下文)
│ ├── 通过 SendMessageTool 与其他队友通信
│ ├── 通过 TaskBoard 共享任务状态
│ └── 完成后向 Lead Agent 报告
│
├── TaskBoard(任务板)
│ ├── 共享的任务状态存储
│ ├── 每个任务有状态(pending/in-progress/done/blocked)
│ ├── 队友可以认领未分配的任务
│ └── 支持任务依赖关系
│
├── Mailbox(邮箱)
│ ├── 每个代理有独立的消息邮箱
│ ├── SendMessageTool 将消息投递到目标邮箱
│ ├── 代理在循环中轮询自己的邮箱
│ └── 支持广播消息(发送给所有队友)
│
└── 生命周期
├── TeamCreateTool → 创建团队 + 生成代理进程
├── 执行阶段 → 代理并行工作 + 消息通信
├── 汇总阶段 → Lead Agent 收集所有结果
└── TeamDeleteTool → 终止所有进程 + 清理资源

代理间的权限传播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
权限传播规则
│
├── AgentTool 子代理
│ ├── 继承父代理的权限模式
│ ├── 继承父代理的规则集
│ └── 会话级权限决策不继承(每个子代理独立)
│
├── Fork 子代理
│ ├── 继承创建时的权限快照
│ ├── 运行期间的权限变更不同步
│ └── shouldAvoidPermissionPrompts 可设置
│
├── Swarm 成员
│ ├── 继承团队创建时的权限配置
│ ├── 协调器可以代理权限决策
│ └── shouldAvoidPermissionPrompts = true(默认)
│
└── Task
├── 继承创建时的权限配置
├── 后台执行时无法交互式确认
└── shouldAvoidPermissionPrompts = true

八、MCP 协议集成

Claude Code 实现了完整的 MCP(Model Context Protocol)客户端,支持五种传输层、OAuth 2.0 认证、会话管理和工具注册。同时,Claude Code 自身也可以作为 MCP 服务器暴露给其他客户端。

MCP 架构总览

五种传输层

传输层 实现 通信方式 适用场景
stdio StdioClientTransport 子进程 stdin/stdout 本地工具(最常用)
SSE SSEClientTransport HTTP Server-Sent Events 远程服务器
Streamable HTTP StreamableHTTPClientTransport HTTP 双向流 新一代远程服务器
Named Pipe 自定义实现 Windows Named Pipe Windows 本地 IPC
Unix Socket 自定义实现 Unix Domain Socket Unix 本地 IPC

传输层选择逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
传输层选择
│
├── 配置中指定 transport?
│ ├── "stdio" → StdioClientTransport
│ ├── "sse" → SSEClientTransport
│ └── "streamable-http" → StreamableHTTPClientTransport
│
├── 配置中有 command?
│ └── → StdioClientTransport(子进程模式)
│
├── 配置中有 url?
│ ├── 尝试 Streamable HTTP
│ ├── 失败 → 降级到 SSE
│ └── SSE 也失败 → 报错
│
└── 配置中有 pipeName / socketPath?
└── → Named Pipe / Unix Socket

MCP 配置来源

MCP 服务器配置从多个来源加载,按优先级合并:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MCP 配置来源(优先级高 → 低)
│
├── 1. CLI 参数
│ └── --mcp-config <path>
│
├── 2. 项目级配置
│ └── .mcp.json(项目根目录)
│
├── 3. 用户级配置
│ └── ~/.claude/mcp_servers.json
│
├── 4. 设置文件中的 mcpServers
│ ├── settings.json(各层级)
│ └── managed settings(企业)
│
├── 5. claude.ai 代理服务器
│ └── fetchClaudeAIMcpConfigsIfEligible()
│ └── 需要 OAuth 认证 + 网络请求
│
└── 6. 企业策略过滤
└── filterMcpServersByPolicy()
└── 可以禁止特定服务器

配置格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_..."
}
},
"remote-api": {
"url": "https://api.example.com/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer ..."
}
}
}
}

连接生命周期

Session Expired 处理

MCP 协议中的 Session Expired 错误需要特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Session Expired 恢复流程
│
├── 检测到 Session Expired 错误
│
├── 1. 关闭当前连接
│ └── 清理所有待处理的请求
│
├── 2. 重新建立连接
│ └── 使用相同的配置参数
│
├── 3. 重新初始化
│ ├── initialize() 协议握手
│ ├── 交换能力
│ └── 重新发现工具
│
├── 4. 重新注册工具
│ ├── 检查工具列表是否变化
│ ├── 新增工具 → 添加到工具池
│ ├── 移除工具 → 从工具池删除
│ └── 变更工具 → 更新 schema
│
└── 5. 重试失败的请求
└── 使用新的 session ID

工具注册与命名

MCP 工具在 Claude Code 中的命名遵循严格的约定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MCP 工具命名约定
│
├── 格式: mcp__{serverName}__{toolName}
│ ├── 双下划线分隔
│ ├── serverName: 配置中的服务器名称
│ └── toolName: 服务器报告的工具名称
│
├── 示例
│ ├── mcp__github__create_issue
│ ├── mcp__filesystem__read_file
│ └── mcp__sentry__search_issues
│
├── 权限规则匹配
│ ├── 精确匹配: "mcp__github__create_issue"
│ ├── 服务器前缀: "mcp__github" → 匹配该服务器所有工具
│ └── 通配符: "mcp__*__read_*" → 匹配所有服务器的 read 工具
│
└── 名称冲突处理
├── 内置工具优先(uniqBy('name'))
└── MCP 工具之间:先注册的优先

MCPTool 包装器

src/tools/MCPTool/MCPTool.ts 将 MCP 工具包装为 Claude Code 的 Tool 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
MCPTool 包装逻辑
│
├── inputSchema
│ ├── 从 MCP 工具的 JSON Schema 转换为 Zod schema
│ └── 支持 $ref 解引用
│
├── call()
│ ├── 序列化输入为 JSON
│ ├── 调用 MCP tools/call
│ ├── 反序列化结果
│ └── 处理错误(超时、连接断开等)
│
├── prompt()
│ ├── 使用 MCP 工具的 description
│ ├── 添加服务器级别的指令(getMcpInstructionsSection)
│ └── 添加 _meta['anthropic/instructions'] 中的指令
│
├── isConcurrencySafe()
│ └── MCP 工具默认 true(假设服务器处理并发)
│
├── isReadOnly()
│ └── 从 _meta['anthropic/readOnly'] 读取
│
├── shouldDefer
│ └── 从 _meta['anthropic/shouldDefer'] 读取
│
├── alwaysLoad
│ └── 从 _meta['anthropic/alwaysLoad'] 读取
│
└── maxResultSizeChars
└── 100_000(MCP 工具统一限制)

MCP 资源系统

除了工具,MCP 还支持资源(Resources)和资源模板(Resource Templates):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MCP 资源系统
│
├── ListMcpResourcesTool
│ ├── 列出服务器提供的所有资源
│ ├── 资源 URI + 描述
│ └── 支持分页
│
├── ReadMcpResourceTool
│ ├── 读取特定资源的内容
│ ├── 支持 URI 模板参数
│ └── 结果作为工具结果返回
│
├── 资源预取
│ ├── prefetchAllMcpResources()
│ ├── 在启动时预取所有资源列表
│ └── 缓存资源元数据
│
└── 资源类型
├── text/* → 文本内容
├── application/json → JSON 数据
└── image/* → Base64 编码图片

OAuth 2.0 认证流程

远程 MCP 服务器支持 OAuth 2.0 认证:

PKCE(Proof Key for Code Exchange):所有 OAuth 流程都使用 PKCE 扩展,防止授权码拦截攻击。code_verifier 是一个随机字符串,code_challenge 是其 SHA-256 哈希。

Token 存储:OAuth token 存储在 ~/.claude/mcp_tokens.json 中,按服务器 URL 索引。Token 包含 access_token、refresh_token、expires_at 等字段。

Elicitation 机制

MCP 服务器可以通过 Elicitation 请求用户输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Elicitation 流程
│
├── MCP 服务器发送 elicitation 请求
│ ├── 请求类型(text、select、confirm)
│ ├── 提示信息
│ └── 可选的默认值
│
├── Claude Code 处理
│ ├── 检查 Elicitation Hook
│ │ └── Hook 可以自动回答(无需用户交互)
│ ├── 无 Hook → 显示 UI 提示
│ │ ├── Terminal UI
│ │ ├── Bridge UI
│ │ └── Channel UI
│ └── 收集用户输入
│
└── 返回结果给 MCP 服务器
└── 用户的输入值

claude.ai MCP 代理

fetchClaudeAIMcpConfigsIfEligible() 从 claude.ai 获取 MCP 服务器配置——这些是 Anthropic 托管的 MCP 代理服务器,通过 SSE 连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
claude.ai MCP 代理
│
├── 资格检查
│ ├── 需要 OAuth 认证(有用户上下文)
│ ├── 需要 feature gate 启用
│ └── CLAUDE_CODE_SIMPLE !== '1'(bare 模式禁用)
│
├── 配置获取
│ ├── GET /api/mcp-configs
│ ├── 返回可用的代理服务器列表
│ └── 每个服务器包含 URL + 认证信息
│
├── 连接
│ ├── 使用 SSE 传输层
│ ├── 附加 OAuth token 作为认证
│ └── 连接超时:6-14 秒/服务器
│
└── 性能影响
├── 每个代理服务器 6-14 秒连接时间
├── bare 模式跳过(节省启动时间)
└── 连接失败不阻塞启动

MCP 服务器模式

Claude Code 自身也可以作为 MCP 服务器运行(src/entrypoints/mcp.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Claude Code 作为 MCP 服务器
│
├── 启动方式
│ └── claude mcp serve
│
├── 暴露的工具
│ ├── 所有内置工具
│ ├── 所有已连接的 MCP 工具(代理转发)
│ └── 自定义工具(通过配置)
│
├── 传输层
│ ├── stdio(默认)
│ └── SSE(通过 --port 参数)
│
└── 使用场景
├── Claude Desktop 集成
├── 其他 MCP 客户端调用 Claude Code 的能力
└── 工具链组合(Claude Code 作为中间层)

Chrome 扩展 MCP

--claude-in-chrome-mcp 启动 Chrome 扩展专用的 MCP 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Chrome MCP 服务器
│
├── 通信方式
│ └── Chrome Native Messaging Host
│ └── --chrome-native-host 参数
│
├── 功能
│ ├── 浏览器页面内容读取
│ ├── 浏览器操作执行
│ └── 与 Claude Code 主进程通信
│
└── 安全
├── 只允许来自 Chrome 扩展的连接
└── 受 Chrome 扩展权限模型保护

MCP 指令注入

getMcpInstructionsSection() 将 MCP 服务器的指令注入到 system prompt 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MCP 指令来源
// 1. 服务器级别指令(initialize 响应中的 instructions 字段)
// 2. 工具级别指令(_meta['anthropic/instructions'])
// 3. 用户配置的指令(settings.json 中的 mcpServerInstructions)

// 注入格式
`## MCP Server Instructions

### ${serverName}
${serverInstructions}

### Tool: ${toolName}
${toolInstructions}
`

这些指令位于 system prompt 的动态区域,不影响静态区域的 Prompt Cache。

XAA 跨应用访问

src/services/mcp/xaa.ts(18.36KB)和 src/services/mcp/xaaIdpLogin.ts(16.37KB)实现了跨应用访问(Cross-Application Access)机制——允许 Claude Code 代表用户访问其他 Anthropic 应用的 MCP 服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
XAA 跨应用访问流程
│
├── 场景
│ ├── Claude Code 需要访问 claude.ai 的 MCP 代理服务器
│ ├── 这些服务器需要验证用户身份
│ └── 用户已经在 Claude Code 中通过 OAuth 认证
│
├── 认证流程
│ ├── 1. 检查本地是否有有效的 XAA token
│ ├── 2. 如果没有 → 发起 XAA IDP 登录
│ │ ├── 使用 Claude Code 的 OAuth token 作为身份证明
│ │ ├── 向 Anthropic IDP 请求 XAA token
│ │ └── XAA token 包含跨应用访问权限
│ ├── 3. 使用 XAA token 连接 MCP 代理服务器
│ └── 4. 缓存 XAA token(有效期内复用)
│
├── 安全模型
│ ├── XAA token 的权限范围受限于用户的 OAuth scope
│ ├── 每个 MCP 服务器有独立的 XAA token
│ ├── Token 过期后自动刷新
│ └── 用户可以随时撤销 XAA 授权
│
└── 与 McpAuthTool 的关系
├── McpAuthTool 处理标准的 MCP OAuth 认证
└── XAA 处理 Anthropic 生态内的跨应用认证

McpAuthTool

src/tools/McpAuthTool/McpAuthTool.ts(7.90KB)提供了 MCP 服务器认证的交互式工具——当 MCP 服务器需要认证但自动认证失败时,模型可以调用此工具引导用户完成认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
McpAuthTool 工作流程
│
├── 触发条件
│ ├── MCP 服务器返回 401/403 错误
│ ├── 或连接时要求认证但无有效 token
│ └── 自动认证(XAA、缓存 token)失败
│
├── 执行流程
│ ├── 1. 识别服务器的认证方式
│ │ ├── OAuth 2.0 → 启动 OAuth 流程
│ │ ├── API Key → 提示用户输入
│ │ └── Bearer Token → 提示用户输入
│ ├── 2. 引导用户完成认证
│ │ ├── 打开浏览器(OAuth)
│ │ ├── 或显示输入提示(API Key)
│ │ └── 等待用户完成
│ ├── 3. 存储认证信息
│ │ └── ~/.claude/mcp_tokens.json
│ └── 4. 重新连接 MCP 服务器
│
└── 与权限系统的交互
├── McpAuthTool 需要用户确认(ask 权限)
└── 防止恶意 MCP 服务器自动触发认证流程

MCP 连接的健康检查

MCP 连接管理器(src/services/mcp/MCPConnectionManager.tsx,8.06KB)实现了连接的健康检查和自动恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
连接健康检查
│
├── 心跳检测
│ ├── 定期发送 ping 请求
│ ├── 超时未响应 → 标记为不健康
│ └── 连续 N 次不健康 → 触发重连
│
├── 工具列表刷新
│ ├── 定期调用 tools/list
│ ├── 检测工具变化(新增/移除/修改)
│ ├── 变化 → 更新 assembleToolPool()
│ └── 通知 UI 更新工具列表
│
├── 错误恢复策略
│ ├── 网络错误 → 指数退避重试(1s, 2s, 4s, 8s, 16s)
│ ├── 服务器崩溃 → 重启子进程(stdio 传输层)
│ ├── Session Expired → 重新初始化
│ └── 认证过期 → 刷新 token 后重连
│
└── 连接池管理
├── 每个 MCP 服务器一个连接
├── 连接复用(同一服务器的多个工具共享连接)
├── 空闲连接超时关闭(节省资源)
└── 最大连接数限制(防止资源耗尽)

九、遥测、安全与隐藏功能

Claude Code 内置了完整的遥测系统、A/B 测试框架、PII 隔离机制,以及多个未公开的隐藏功能。这些系统共同支撑了产品的数据驱动迭代和安全合规。

双层遥测管道

遥测系统分为两层——第一方事件日志(1P Event Logging)和第三方分析(Sentry/Statsig),各自有不同的数据范围和隐私级别。

1P Event Logging

src/services/firstPartyEventLogger.ts 实现了第一方事件日志系统:

初始化流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
initialize1PEventLogging()
│
├── 延迟加载 OpenTelemetry SDK
│ └── Promise.all([import('sdk-logs'), import('growthbook')])
│
├── 创建 LoggerProvider
│ ├── BatchLogRecordExporter
│ │ ├── 批量大小: 50 条
│ │ ├── 发送间隔: 5 秒
│ │ └── 队列上限: 1000 条
│ └── 目标端点: Anthropic 遥测 API
│
├── 注册 GrowthBook 刷新回调
│ └── onGrowthBookRefresh() → reinitialize1PEventLoggingIfConfigChanged()
│ └── 配置变更时重新初始化(如端点 URL 变更)
│
└── 注册清理函数
└── registerCleanup(() => loggerProvider.shutdown())

事件类型:

事件 触发时机 包含数据
tengu_timer 启动完成 启动耗时、各阶段时间戳
tool_use 工具调用 工具名称、耗时、成功/失败
api_call API 请求 模型、token 数、延迟
permission_decision 权限决策 工具、决策、来源
compact 压缩触发 压缩前后 token 数
session_end 会话结束 总轮次、总 token、总耗时
error 错误发生 错误类型、堆栈(脱敏)

PII 隔离:PROTO* 字段

遥测数据中的 PII(个人可识别信息)通过 _PROTO_* 前缀字段隔离:

1
2
3
4
5
6
7
8
9
10
11
12
// 遥测事件示例
{
event: 'tool_use',
tool_name: 'BashTool', // 非 PII
duration_ms: 1234, // 非 PII
success: true, // 非 PII

// PII 字段——存储在隔离的数据管道中
_PROTO_command: 'git commit -m "fix: user login"', // 可能包含用户信息
_PROTO_file_path: '/home/user/project/auth.ts', // 包含用户路径
_PROTO_error_message: 'Connection refused to db.internal.company.com',
}

隔离机制:

  • _PROTO_* 字段在传输前被分离到独立的数据流
  • 非 PII 字段进入标准分析管道(可聚合、可查询)
  • PII 字段进入受限管道(更短的保留期、更严格的访问控制)
  • 客户端可以通过设置完全禁用 PII 字段的发送

GrowthBook A/B 测试

src/services/growthbook.ts 集成了 GrowthBook SDK,用于 feature flag 和 A/B 测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GrowthBook 集成
│
├── 初始化
│ ├── 加载 GrowthBook SDK
│ ├── 设置用户属性(user ID、组织、计划等)
│ ├── 获取 feature flag 配置
│ └── 注册刷新回调
│
├── Feature Flag 评估
│ ├── feature('FLAG_NAME') → boolean
│ ├── 编译时 DCE(Dead Code Elimination)
│ │ └── 未启用的 feature 代码在构建时被移除
│ └── 运行时评估(基于用户属性)
│
├── A/B 测试
│ ├── 基于用户 ID 的确定性分桶
│ ├── 支持多变体实验
│ └── 曝光事件自动记录
│
└── 刷新机制
├── 定期轮询(默认 5 分钟)
├── 配置变更 → 触发回调
└── 回调可以重新初始化依赖系统

Feature Flag 的编译时门控:

1
2
3
4
5
6
// feature() 函数在构建时被评估
// 如果 flag 未启用,整个代码块被 DCE 移除
if (feature('AGENT_SWARMS')) {
// 这段代码只在 AGENT_SWARMS 启用时存在于 bundle 中
tools.push(TeamCreateTool, TeamDeleteTool)
}

已知的 Feature Flag 清单(部分):

Flag 功能 状态
AGENT_SWARMS 多代理团队协作 实验性
BG_SESSIONS 后台会话管理 实验性
BRIDGE_MODE Bridge 远程控制 实验性
CONTEXT_COLLAPSE 上下文折叠 渐进推出
HISTORY_SNIP 历史裁剪 渐进推出
TOOL_SEARCH 工具搜索 渐进推出
TASKS 后台任务系统 实验性
WORKTREE_MODE Git Worktree 模式 实验性
DAEMON 守护进程模式 实验性
KAIROS 定时任务/推送通知 实验性
TEMPLATES 模板任务 实验性
BYOC_ENVIRONMENT_RUNNER BYOC 环境运行器 实验性
SELF_HOSTED_RUNNER 自托管运行器 实验性
AUTO_MEMORY 自动记忆提取 渐进推出
BASH_CLASSIFIER Bash 命令分类器 渐进推出
TRANSCRIPT_CLASSIFIER 对话分类器 渐进推出
ABLATION_BASELINE L0 消融基线 Ant-only
CHICAGO_MCP Computer Use MCP 实验性

Sentry 错误追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Sentry 集成
│
├── 初始化
│ ├── initializeTelemetryAfterTrust()
│ │ └── 必须在 Trust Dialog 之后(用户同意遥测)
│ ├── 配置 DSN、环境、版本
│ └── 设置采样率
│
├── 错误捕获
│ ├── 未捕获异常 → 自动上报
│ ├── 未处理的 Promise rejection → 自动上报
│ ├── 手动 captureException() → 主动上报
│ └── 面包屑(breadcrumbs)→ 上下文追踪
│
├── 数据脱敏
│ ├── 文件路径 → 相对路径
│ ├── 用户名 → 哈希
│ ├── API Key → 移除
│ └── 自定义 beforeSend 过滤器
│
└── 性能监控
├── Transaction 追踪
├── API 调用延迟
└── 工具执行耗时

Undercover 模式

Undercover 模式是一个隐藏功能——当启用时,Claude Code 伪装为其他 AI 编码助手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Undercover 模式
│
├── 触发方式
│ └── 环境变量或内部配置(未公开)
│
├── 行为变化
│ ├── System Prompt 中移除 "Claude" 相关引用
│ ├── 替换为通用的 "AI Assistant" 描述
│ ├── 移除 Anthropic 特定的指令
│ └── 调整语气和风格
│
├── 用途
│ ├── 竞品分析和基准测试
│ ├── 避免模型在输出中自我引用
│ └── 第三方集成中的白标需求
│
└── 实现
├── getSimpleIntroSection() 中的条件分支
├── 检查 undercover 配置
└── 替换身份声明文本

CYBER_RISK_INSTRUCTION

System Prompt 中包含一段安全指令 CYBER_RISK_INSTRUCTION,用于防止模型被利用进行恶意操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CYBER_RISK_INSTRUCTION 内容(概要)
│
├── 禁止生成恶意代码
│ ├── 恶意软件、病毒、蠕虫
│ ├── 漏洞利用代码
│ └── 社会工程攻击脚本
│
├── 禁止协助攻击
│ ├── 网络入侵
│ ├── 数据窃取
│ └── 权限提升
│
├── 安全编码指导
│ ├── 输入验证
│ ├── 输出编码
│ └── 安全默认值
│
└── 注入位置
└── getSimpleIntroSection() 的身份声明之后
└── 位于 system prompt 的最前面(最高优先级)

性能追踪系统

profileCheckpoint():

1
2
3
4
5
6
7
8
9
// 在关键节点记录时间戳
profileCheckpoint('cli_entry')
profileCheckpoint('init_function_start')
profileCheckpoint('init_configs_enabled')
// ... 更多检查点
profileCheckpoint('cli_after_main_complete')

// 所有检查点在 tengu_timer 事件中上报
// 用于分析启动性能瓶颈

Event Loop Stall Detector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
事件循环阻塞检测
│
├── 原理
│ ├── 注册一个 setInterval(间隔 500ms)
│ ├── 记录每次回调的实际时间
│ └── 如果实际间隔 > 预期间隔 + 阈值 → 记录阻塞
│
├── 阈值
│ └── 500ms(主线程被阻塞超过 500ms 时触发)
│
├── 记录内容
│ ├── 阻塞持续时间
│ ├── 阻塞发生时的调用栈(如果可用)
│ └── 当前正在执行的操作
│
└── 用途
├── 诊断启动卡顿
├── 识别同步 I/O 操作
└── 发现 CPU 密集型计算

Prompt Cache 四层优化

Prompt Cache 是 Claude Code 最重要的性能优化之一——通过缓存 system prompt 和工具 schema,避免每次 API 调用都重新处理这些内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Prompt Cache 四层优化
│
├── Layer 1: System Prompt 静态/动态分区
│ ├── 静态部分(SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之前)
│ │ └── 跨用户、跨会话可缓存
│ ├── 动态部分(BOUNDARY 之后)
│ │ └── 会话级缓存
│ └── 分区保证静态部分的 cache key 稳定
│
├── Layer 2: 工具 Schema 排序稳定性
│ ├── 内置工具按名称排序(前缀)
│ ├── MCP 工具按名称排序(后缀)
│ └── 增减 MCP 工具不影响内置工具的位置
│
├── Layer 3: cache_control 标记
│ ├── ephemeral breakpoint 标记
│ ├── 告诉 API 哪些内容可以缓存
│ └── normalizeMessages() 中注入
│
└── Layer 4: 子代理 Prompt Cache 共享
├── AgentTool 子代理继承主代理的静态 prompt
├── 子代理首次调用命中主代理的缓存
└── 节省约 15% 的 token 成本

内存管理

Claude Code 作为长时间运行的 Node.js 进程,内存管理至关重要:

已知的内存泄漏场景:

场景 原因 缓解措施
消息历史无限增长 长会话积累大量消息 autoCompact 定期压缩
工具结果未释放 大文件内容保留在内存 applyToolResultBudget 持久化到磁盘
MCP 连接泄漏 服务器崩溃后连接未清理 Session Expired 检测 + 重连
Sentry 面包屑 面包屑队列无限增长 设置 maxBreadcrumbs 上限
GrowthBook 缓存 Feature flag 缓存未过期 定期刷新 + 内存上限
子进程句柄 Fork 子进程未正确终止 registerCleanup + SIGKILL 兜底

CCR 容器环境:CLAUDE_CODE_REMOTE=true 时设置 --max-old-space-size=8192(8GB),防止容器 OOM。

优雅退出

setupGracefulShutdown() 注册了多层退出处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
优雅退出流程
│
├── 触发信号
│ ├── SIGINT(Ctrl+C)
│ ├── SIGTERM(进程管理器)
│ └── SIGHUP(终端关闭)
│
├── 退出序列
│ ├── 1. 执行 SessionEnd Hook
│ ├── 2. 停止所有后台任务
│ ├── 3. 关闭 MCP 连接
│ ├── 4. 关闭 LSP 服务器
│ ├── 5. 清理 Swarm 团队
│ ├── 6. 刷新遥测缓冲区
│ ├── 7. 关闭 Sentry
│ └── 8. 清理临时文件(Scratchpad)
│
├── 超时保护
│ ├── 每个清理步骤有独立超时
│ ├── 总超时 10 秒
│ └── 超时后强制退出(process.exit(1))
│
└── registerCleanup() 注册的清理函数
├── shutdownLspServerManager
├── cleanupSessionTeams
├── loggerProvider.shutdown()
└── 自定义清理函数

安全审计日志

所有安全相关的操作都记录审计日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
审计日志记录的事件
│
├── 权限决策
│ ├── 工具名称、输入摘要
│ ├── 决策结果(allow/deny/ask)
│ ├── 决策来源(rule/classifier/user/hook)
│ └── 规则匹配详情
│
├── Hook 执行
│ ├── Hook 类型(command/prompt/http)
│ ├── Hook 来源(managed/user/project)
│ ├── 执行结果
│ └── 执行耗时
│
├── 认证事件
│ ├── OAuth 登录/刷新/失败
│ ├── API Key 使用
│ └── Token 过期
│
├── 配置变更
│ ├── 设置文件修改
│ ├── 权限规则变更
│ └── MCP 服务器增减
│
└── 存储位置
├── 1P Event Logging(远程)
├── 本地日志文件(~/.claude/logs/)
└── Sentry(错误相关)

企业管控能力

企业管理员通过 managed settings 和策略控制 Claude Code 的行为:

管控项 配置路径 说明
工具权限 managed.alwaysDeny 禁止特定工具
MCP 服务器 managed.mcpServers 强制/禁止特定服务器
Hook managed.hooks 强制执行的 Hook
模型 managed.model 强制使用特定模型
遥测 managed.telemetry 控制遥测数据范围
远程控制 managed.allow_remote_control 允许/禁止 Bridge 模式
权限模式 managed.permissionMode 强制权限模式
网络 managed.proxy 强制代理配置

策略限制:initializePolicyLimitsLoadingPromise() 在启动时加载企业策略限制——这些限制不能被用户或项目设置覆盖。

隐藏的调试功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
隐藏调试功能
│
├── --dump-system-prompt
│ ├── 输出完整的渲染后 system prompt
│ ├── Ant-only(需要内部认证)
│ └── 用于调试 prompt 组装问题
│
├── CLAUDE_CODE_DEBUG=1
│ ├── 启用详细日志输出
│ ├── 显示 API 请求/响应详情
│ └── 显示工具执行详情
│
├── CLAUDE_CODE_ABLATION_BASELINE
│ ├── L0 消融基线模式
│ ├── 禁用所有高级功能
│ ├── 用于 A/B 测试基线对比
│ └── Ant-only
│
├── profileCheckpoint 输出
│ ├── 启动性能详细时间线
│ └── 通过遥测事件上报
│
└── Event Loop Stall 日志
├── 主线程阻塞检测
└── 输出到本地日志

Datadog 集成

src/services/analytics/datadog.ts(9.19KB)实现了 Datadog APM 集成,提供更细粒度的性能监控:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Datadog 集成
│
├── 指标类型
│ ├── 计数器(Counter)
│ │ ├── api_calls_total // API 调用总数
│ │ ├── tool_executions_total // 工具执行总数
│ │ ├── permission_decisions // 权限决策次数
│ │ └── compact_triggers // 压缩触发次数
│ │
│ ├── 直方图(Histogram)
│ │ ├── api_latency_ms // API 延迟分布
│ │ ├── tool_execution_ms // 工具执行时间分布
│ │ ├── startup_time_ms // 启动时间分布
│ │ └── compact_duration_ms // 压缩耗时分布
│ │
│ └── 仪表盘(Gauge)
│ ├── context_tokens_used // 当前上下文 token 使用量
│ ├── active_mcp_connections // 活跃 MCP 连接数
│ └── memory_entries_count // 记忆条目数
│
├── 标签(Tags)
│ ├── model: claude-opus-4-6
│ ├── entrypoint: cli / sdk / mcp
│ ├── user_type: ant / external
│ └── permission_mode: default / auto / bypass
│
└── 与 1P Event Logging 的关系
├── 1P 记录产品事件(用户行为分析)
└── Datadog 记录性能指标(运维监控)

环境指纹收集

src/services/analytics/metadata.ts(32.80KB)收集详细的环境元数据,用于遥测分析和问题诊断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
环境指纹收集的字段
│
├── 平台信息
│ ├── os: 'darwin' | 'linux' | 'win32'
│ ├── arch: 'x64' | 'arm64'
│ ├── platform_version: '14.2.1'(macOS 版本)
│ ├── kernel_version: '6.5.0-44-generic'(Linux 内核)
│ └── is_wsl: boolean(Windows Subsystem for Linux)
│
├── 终端信息
│ ├── terminal: 'iTerm2' | 'Terminal.app' | 'Windows Terminal' | ...
│ ├── shell: 'zsh' | 'bash' | 'fish' | 'powershell'
│ ├── term_program: $TERM_PROGRAM
│ ├── term_colors: $COLORTERM
│ └── columns / rows(终端尺寸)
│
├── IDE 信息
│ ├── ide: 'vscode' | 'jetbrains' | 'cursor' | 'windsurf' | ...
│ ├── ide_version: '1.85.0'
│ └── is_remote: boolean(SSH / Remote Desktop)
│
├── CI/CD 信息
│ ├── is_ci: boolean
│ ├── ci_provider: 'github-actions' | 'gitlab-ci' | 'jenkins' | ...
│ ├── ci_job_id: string
│ └── ci_branch: string
│
├── 运行时信息
│ ├── node_version: '20.10.0'
│ ├── bun_version: '1.1.0'(如果使用 Bun)
│ ├── claude_code_version: '2.1.88'
│ └── npm_version: '10.2.3'
│
├── 项目信息
│ ├── git_remote_url_hash: SHA-256(隐私保护)
│ ├── file_count: 1234(项目文件数)
│ ├── has_package_json: boolean
│ ├── has_tsconfig: boolean
│ └── primary_language: 'typescript' | 'python' | ...
│
└── 用户信息
├── user_id_hash: SHA-256(隐私保护)
├── organization_id: string(OAuth 用户)
├── plan: 'free' | 'pro' | 'team' | 'enterprise'
└── account_age_days: number

隐私保护:所有可能包含 PII 的字段都经过哈希处理——git_remote_url_hash 使用 SHA-256 哈希仓库 URL,user_id_hash 哈希用户 ID。这允许聚合分析(“有多少不同的仓库使用了 Claude Code”)而不暴露具体的仓库 URL。

模型代号体系

源码中使用动物代号指代不同的模型系列:

代号 模型 说明
Capybara Claude Sonnet 系列 主力模型,平衡性能和成本
Tengu Claude Opus 系列 最强模型,用于复杂任务
Fennec Claude Haiku 系列 轻量模型,用于分类器和摘要
Numbat 下一代模型 开发中,源码中有占位符

模型 ID 映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const MODEL_IDS = {
opus: 'claude-opus-4-6',
sonnet: 'claude-sonnet-4-6',
haiku: 'claude-haiku-4-5-20251001',
}

const FRONTIER_MODEL_NAME = 'Claude Opus 4.6'

// 知识截止日期
function getKnowledgeCutoff(model: string): string {
if (model.includes('opus-4-6')) return 'May 2025'
if (model.includes('sonnet-4-6')) return 'August 2025'
if (model.includes('haiku-4-5')) return 'Early 2025'
return 'Early 2025'
}

@[MODEL LAUNCH] 标记:源码中有 @[MODEL LAUNCH] 注释标记需要在新模型发布时更新的位置——包括模型 ID、知识截止日期、默认参数等。

模型迁移脚本

src/migrations/ 目录包含模型迁移脚本,用于在模型更新时自动迁移用户配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
模型迁移脚本
│
├── 迁移场景
│ ├── 模型 ID 变更(如 claude-3-opus → claude-opus-4)
│ ├── 默认模型切换(如 Sonnet 4.5 → Sonnet 4.6)
│ ├── 模型参数变更(如 max_tokens 默认值变化)
│ └── 模型能力变更(如新增 thinking 支持)
│
├── 迁移流程
│ ├── 1. 检查用户配置中的模型设置
│ ├── 2. 如果使用了旧模型 ID → 自动替换为新 ID
│ ├── 3. 如果使用了已废弃的参数 → 迁移到新参数
│ ├── 4. 记录迁移日志
│ └── 5. 通知用户(如果有破坏性变更)
│
├── 已知迁移
│ ├── Fennec → Haiku 4.5(模型代号到正式名称)
│ ├── Sonnet 4.5 → Sonnet 4.6(默认模型升级)
│ └── Opus 4.5 → Opus 4.6(默认模型升级)
│
└── 迁移版本追踪
├── ~/.claude/migration_version
├── 记录已执行的迁移版本
└── 防止重复执行

远程托管设置

src/services/remoteManagedSettings/index.ts(21.04KB)实现了远程托管设置——企业管理员可以通过 API 远程控制 Claude Code 的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
远程托管设置
│
├── 加载流程
│ ├── 启动时发起 API 请求获取远程设置
│ ├── 每小时轮询更新
│ ├── 设置缓存在本地(离线时使用缓存)
│ └── 设置变更触发 settingsChangeDetector
│
├── 可控制的设置
│ ├── 工具权限(alwaysDeny、alwaysAsk)
│ ├── MCP 服务器白名单/黑名单
│ ├── 模型限制(强制使用特定模型)
│ ├── 功能开关(禁用特定功能)
│ └── 安全策略(强制沙箱、禁止 bypass 模式)
│
├── Killswitch 机制
│ ├── 远程设置可以包含 killswitch 标志
│ ├── 激活时显示阻塞对话框
│ ├── 用户无法绕过(必须等待管理员解除)
│ └── 用于紧急安全事件响应
│
└── 安全检查
├── securityCheck.tsx(10.34KB)
├── 验证远程设置的签名
├── 防止中间人篡改设置
└── 设置过期检测(过期的设置不生效)

Beta 功能管理

src/utils/betas.ts(15.74KB)管理 API 级别的 beta 功能:

1
2
3
4
5
6
7
8
9
10
11
12
// 已知的 Beta 功能
const BETAS = {
'task-budgets-2026-03-13': true, // Task Budget API
'prompt-caching-2024-07-31': true, // Prompt Cache
'max-tokens-3-5-sonnet-2024-07-15': true, // 扩展 max_tokens
'output-128k-2025-02-19': true, // 128K 输出
'interleaved-thinking-2025-05-14': true, // 交错思考
'token-efficient-tools-2025-02-19': true, // Token 高效工具
}

// Beta 功能通过 API 请求头传递
// x-anthropic-beta: task-budgets-2026-03-13,prompt-caching-2024-07-31,...

未来路线图线索

源码中包含多个未来功能的线索:

线索 来源 推测
KAIROS Feature flags、system prompt 完全自主代理模式(tick 心跳、推送通知、PR 订阅)
Numbat 模型代号映射 下一代模型(可能是 Claude 5 系列)
语音模式 useVoiceIntegration.tsx(97.79KB) Push-to-talk 语音输入,WebSocket STT
WebBrowserTool Feature flag、工具注册 浏览器自动化(代号 bagel)
Buddy 系统 源码中的类型定义 虚拟宠物伙伴(18 物种、5 稀有度)
Opus 4.7 / Sonnet 4.8 @[MODEL LAUNCH] 标记 下一代模型版本
Plugin 系统 src/plugins/(多文件) 第三方插件生态
Skills 系统 src/skills/(多文件) 可复用的技能模板
🌙