Claude Code 源码架构深度解析:1884 个文件背后的 AI 编程工具设计哲学
近日 Claude Code 源码意外泄漏,作为一个深度使用 Claude Code 的开发者,我对这份 33MB、1884 个 TypeScript 文件的代码库做了一次完整的架构分析。本文将从宏观到微观,拆解这个当前最强 AI 编程助手的内部设计。
一、项目概览
| 指标 | 数值 |
|---|---|
| 文件总数 | 1,884 个 (.ts/.tsx) |
| 代码体积 | 33 MB |
| 核心入口文件 | main.tsx (4,683 行) |
| 查询引擎 | QueryEngine.ts (1,295 行) + query.ts (1,729 行) |
| 内置工具 | 43 个独立模块 |
| 斜杠命令 | 100+ 个 |
| React Hooks | 85 个 |
| 服务模块 | 35+ 个 |
| 技术栈 | TypeScript + React + Ink (终端 UI) + Zod + Bun |
二、整体架构:五层设计
先上全局架构图,后面逐层拆解:
三、入口层:毫秒级的启动优化
3.1 快速路径(Fast Path)
cli.tsx 是整个应用的入口,只有 302 行,但信息密度极高。
// cli.tsx — 第一行就是 feature flag
import { feature } from 'bun:bundle';
// 顶层副作用:环境准备
process.env.COREPACK_ENABLE_AUTO_PIN = '0'; // 防止 corepack 污染 package.json
// CCR 远程环境设置 8GB 堆上限
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
process.env.NODE_OPTIONS = existing
? `${existing} --max-old-space-size=8192`
: '--max-old-space-size=8192';
}
关键设计:所有 import 都是动态的。--version 路径零模块加载:
async function main(): Promise<void> {
const args = process.argv.slice(2);
// 快速路径 1: --version — 零 import,直接输出
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
console.log(`${MACRO.VERSION} (Claude Code)`); // 编译时内联
return;
}
// 快速路径 2: --dump-system-prompt (Ant-only, 编译时消除)
if (feature('DUMP_SYSTEM_PROMPT') && args[0] === '--dump-system-prompt') { ... }
// 快速路径 3: Chrome 原生宿主
if (process.argv[2] === '--chrome-native-host') { ... }
// 快速路径 4: Daemon worker (内部 supervisor 用)
if (feature('DAEMON') && args[0] === '--daemon-worker') { ... }
// 快速路径 5: Bridge 远程控制
if (feature('BRIDGE_MODE') && (args[0] === 'remote-control' || args[0] === 'rc')) { ... }
}
每个分支都有 profileCheckpoint() 调用,方便 Anthropic 内部做启动性能分析。
3.2 反调试机制
外部构建包含一个反调试检查,检测到 --inspect 或 Node Inspector 活跃时直接退出:
// 外部构建检测调试器
if ("external" !== 'ant' && isBeingDebugged()) {
process.exit(1); // 静默退出,无错误信息
}
这里 "external" 是编译时替换的字符串——Anthropic 内部构建替换为 'ant',外部构建保持 'external'。所以只有外部用户会被拦截,内部开发者不受影响。
3.3 main.tsx:4683 行的超级编排器
main.tsx 是我见过最复杂的单文件初始化逻辑。它的前 20 行就包含了三个并行的副作用:
// 第 1 行:标记入口时间戳
import { profileCheckpoint } from './utils/startupProfiler.js';
profileCheckpoint('main_tsx_entry');
// 第 2 行:火速启动 MDM 子进程(macOS plutil / Windows reg query)
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';
startMdmRawRead();
// 第 3 行:并行读取 macOS Keychain(OAuth token + 旧版 API key)
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';
startKeychainPrefetch();
这三个操作在模块加载阶段(import 副作用)就开始执行,比 main() 函数早 135ms 以上。这是因为后续 135ms 花在加载其余 100+ 个 import 上——这段时间内 MDM 和 Keychain 的子进程已经在后台跑完了。
3.4 迁移系统
main.tsx 包含一个版本化的迁移系统,当前版本号是 11:
const CURRENT_MIGRATION_VERSION = 11;
function runMigrations(): void {
if (getGlobalConfig().migrationVersion !== CURRENT_MIGRATION_VERSION) {
migrateAutoUpdatesToSettings();
migrateSonnet45ToSonnet46(); // 模型名升级
migrateOpusToOpus1m(); // Opus → Opus 1M
migrateBypassPermissionsAcceptedToSettings(); // 权限状态迁移
// ... 更多迁移
saveGlobalConfig(prev => ({
...prev,
migrationVersion: CURRENT_MIGRATION_VERSION
}));
}
}
每次版本号变更时,所有迁移函数幂等执行——已经迁移过的会 early return。
3.5 REPL 启动
最终通过 launchRepl() 启动交互界面:
// replLauncher.tsx — 动态 import App 和 REPL
export async function launchRepl(root, appProps, replProps, renderAndRun) {
const { App } = await import('./components/App.js');
const { REPL } = await import('./screens/REPL.js');
await renderAndRun(root, <App {...appProps}><REPL {...replProps} /></App>);
}
App 组件包裹了 ThemeProvider(在 ink.ts 中注入),提供亮/暗主题支持。Ink 本身是主题无关的——Claude Code 通过全局包装层注入主题。
四、状态管理:34 行的极简 Store
整个应用的状态管理只有 34 行代码,不依赖任何第三方库:
// state/store.ts — 完整源码
type Listener = () => void
type OnChange<T> = (args: { newState: T; oldState: T }) => void
export type Store<T> = {
getState: () => T
setState: (updater: (prev: T) => T) => void
subscribe: (listener: Listener) => () => void
}
export function createStore<T>(
initialState: T,
onChange?: OnChange<T>,
): Store<T> {
let state = initialState
const listeners = new Set<Listener>()
return {
getState: () => state,
setState: (updater: (prev: T) => T) => {
const prev = state
const next = updater(prev)
if (Object.is(next, prev)) return // 核心:引用相等检查
state = next
onChange?.({ newState: next, oldState: prev })
for (const listener of listeners) listener()
},
subscribe: (listener: Listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
配合 React 18 的 useSyncExternalStore 实现最小化重渲染:
// state/AppState.tsx
export function useAppState<T>(selector: (state: AppState) => T): T {
const store = useAppStore();
return useSyncExternalStore(
store.subscribe,
() => selector(store.getState()),
);
}
为什么不用 Zustand 或 Redux? 因为 Claude Code 的状态更新模式非常简单——没有中间件、没有 devtools、没有异步 action。一个 Object.is() + Set<Listener> 就够了。34 行代码 vs Zustand 的 1000+ 行,性能更好,依赖更少。
AppState 的形状
export type AppState = DeepImmutable<{
settings: SettingsJson
mainLoopModel: ModelSetting
toolPermissionContext: ToolPermissionContext
verbose: boolean
statusLineText: string | undefined
isBriefOnly: boolean
spinnerTip?: string
kairosEnabled: boolean
// Bridge/Remote 状态
remoteSessionUrl: string | undefined
remoteConnectionStatus: 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
replBridgeEnabled: boolean
replBridgeConnected: boolean
// ...
}> & {
// 可变部分
tasks: { [taskId: string]: TaskState }
agentNameRegistry: Map<string, AgentId>
foregroundedTaskId?: string
mcp: {
clients: MCPServerConnection[]
tools: Tool[]
commands: Command[]
resources: Record<string, ServerResource[]>
}
}
注意:大部分字段是 DeepImmutable(只读递归类型),但 tasks 和 mcp 是可变的——因为它们需要高频更新。
五、查询引擎:AsyncGenerator 驱动的对话循环
5.1 QueryEngine 类
QueryEngine 管理一次对话的完整生命周期,核心是一个异步生成器:
export type QueryEngineConfig = {
cwd: string
tools: Tools
commands: Command[]
mcpClients: MCPServerConnection[]
agents: AgentDefinition[]
canUseTool: CanUseToolFn
getAppState: () => AppState
setAppState: (f: (prev: AppState) => AppState) => void
readFileCache: FileStateCache
customSystemPrompt?: string
appendSystemPrompt?: string
thinkingConfig?: ThinkingConfig
maxTurns?: number // SDK 模式下限制轮数
maxBudgetUsd?: number // 成本预算
// ...
}
export class QueryEngine {
private mutableMessages: Message[] = [] // 跨 turn 持久化
private fileStateCache: FileStateCache // 文件读取缓存
private totalUsage: NonNullableUsage // 累计 token
private permissionDenials: SDKPermissionDenial[] = []
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean }
): AsyncGenerator<SDKMessage, void, unknown> {
// 逐条 yield 消息,实现真正的流式响应
}
}
5.2 数据流详解
用户输入 "帮我写一个 TODO 应用"
│
▼
processUserInput()
│ 解析斜杠命令 (/commit, /review 等)
│ 展开粘贴内容 ([Pasted text #1 +10 lines])
│ 处理附件(图片、PDF)
▼
fetchSystemPromptParts()
│ ① getSystemContext() — Git状态(memoized, 一次对话只算一次)
│ ├─ getBranch()
│ ├─ getDefaultBranch()
│ ├─ git status --short (截断至2000字符)
│ ├─ git log --oneline -n 5
│ └─ git config user.name
│ ② getUserContext() — claude.md 发现 + 当前日期
│ ③ getCoordinatorUserContext() — Coordinator 模式专用
│ ④ 技能前端matter + 工具描述
▼
query() — 核心查询函数 (1729行)
│
├─ normalizeMessagesForAPI()
│ 过滤掉 progress/system 消息
│ 将内部消息格式转为 API 格式
│
├─ queryModelWithStreaming()
│ 调用 Claude API (带 prompt cache)
│ yield AssistantMessage (流式)
│
├─ 提取 tool_use blocks
│
├─ StreamingToolExecutor.execute()
│ ├─ 并发安全工具 → Promise.all() 并行执行
│ └─ 非安全工具 → 顺序执行
│
├─ applyToolResultBudget()
│ 大结果截断,防止上下文膨胀
│
├─ executePostSamplingHooks()
│ Stop hook / PostToolUse hook
│
├─ calculateTokenWarningState()
│ 检查是否接近上下文窗口上限
│
└─ autoCompact / snipCompact (如果启用)
压缩旧消息,释放上下文空间
5.3 上下文压缩策略
当对话接近 token 上限时,有三种压缩策略(通过 Feature Gate 控制):
// services/compact/autoCompact.ts
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000 // 触发阈值缓冲
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000 // 警告阈值
export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
}
// 有效上下文窗口 = 总窗口 - 预留输出空间(20000)
export function getEffectiveContextWindowSize(model: string): number {
const reservedTokensForSummary = Math.min(
getMaxOutputTokensForModel(model),
20_000
)
return contextWindow - reservedTokensForSummary
}
三种策略:
| 策略 | Feature Gate | 说明 |
|---|---|---|
| Auto Compact | 默认启用 | 到达阈值时压缩旧消息为摘要 |
| Reactive Compact | REACTIVE_COMPACT | 主动式压缩,更激进 |
| History Snip | HISTORY_SNIP | 直接截断历史,仅保留最近消息 |
| Context Collapse | CONTEXT_COLLAPSE | 上下文折叠,语义压缩 |
六、工具系统:43 个模块的插件式架构
6.1 工具注册
tools.ts 是工具的注册中心,390 行代码管理所有工具的生命周期:
// tools.ts — 所有工具的注册
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
// Ant 内部构建嵌入了 bfs/ugrep,不需要独立的 Glob/Grep
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
SkillTool,
// ... 更多工具
// Feature-gated 工具 (编译时消除)
...(WebBrowserTool ? [WebBrowserTool] : []),
...(SleepTool ? [SleepTool] : []),
...(SnipTool ? [SnipTool] : []),
...(WorkflowTool ? [WorkflowTool] : []),
// 仅在 Ant 内部构建启用
...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
...(REPLTool ? [REPLTool] : []),
// 仅在测试环境
...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
]
}
6.2 工具池组装
内置工具和 MCP 工具通过 assembleToolPool() 合并:
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 关键设计:按 name 排序,保证 prompt cache 稳定性
// 如果 MCP 工具插入到内置工具之间,会导致所有下游 cache key 失效
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
// uniqBy 保留插入顺序 → 内置工具同名时优先
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}
6.3 Simple 模式
当 CLAUDE_CODE_SIMPLE=1 时,工具池缩减到最小:
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool]
// Coordinator 模式额外加入 Agent + TaskStop
if (coordinatorModeModule?.isCoordinatorMode()) {
simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool())
}
return filterToolsByDenyRules(simpleTools, permissionContext)
}
6.4 工具接口
每个工具实现统一的 Tool 接口(Tool.ts, 792 行):
type Tool<Input, Output> = {
name: string
aliases?: string[] // 别名
searchHint?: string // ToolSearch 匹配用
shouldDefer?: boolean // 是否延迟加载(省token)
alwaysLoad?: boolean // 是否总是加载
// 核心
call(args, context, canUseTool, parentMessage, onProgress?)
description(): Promise<string>
prompt(): Promise<string> // 给模型看的使用说明
inputJSONSchema: ToolInputJSONSchema // Zod → JSON Schema
// 权限
checkPermissions(): Promise<PermissionResult>
validateInput(): Promise<ValidationResult>
// 属性
isConcurrencySafe(): boolean // 能否并行
isReadOnly(): boolean // 只读?
isDestructive(): boolean // 破坏性?
// UI 渲染
renderToolUseMessage()
renderToolResultMessage()
renderToolUseProgressMessage()
}
6.5 BashTool 实现细节
// BashTool 输入 Schema
const fullInputSchema = z.strictObject({
command: z.string(),
timeout: z.number().optional(),
description: z.string().optional(),
run_in_background: z.boolean().optional(),
dangerouslyDisableSandbox: z.boolean().optional(),
_simulatedSedEdit: z.object({ // 隐藏字段:sed 编辑模拟
filePath: z.string(),
newContent: z.string()
}).optional()
})
// 命令分类(用于 UI 折叠展示)
const BASH_SEARCH_COMMANDS = new Set([
'find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'
])
const BASH_READ_COMMANDS = new Set([
'cat', 'head', 'tail', 'less', 'more', 'wc', 'stat', 'file', 'jq', 'awk'
])
const BASH_SILENT_COMMANDS = new Set([
'mv', 'cp', 'rm', 'mkdir', 'rmdir', 'chmod', 'chown', 'touch', 'ln', 'cd'
])
6.6 FileEditTool 的原子写入
文件编辑实现了无 async 间隙的原子写入,防止竞态条件:
// 关键:staleness 检查和写入之间不能有 async 操作
const lastWriteTime = getFileModificationTime(absoluteFilePath)
if (lastWriteTime > readTimestamp.timestamp) {
// 文件在读取后被外部修改了
throw new Error(FILE_UNEXPECTEDLY_MODIFIED_ERROR)
}
// 同步读取 + 同步写入,中间无 async yield
const { content, encoding, lineEndings } = readFileForEdit(absoluteFilePath)
const { patch, updatedFile } = getPatchForEdit({
filePath, originalFileContents: content,
oldString: actualOldString, newString, replaceAll
})
writeTextContent(absoluteFilePath, updatedFile, encoding, lineEndings) // 原子写入
// 立即更新 readFileState 缓存
readFileState.set(absoluteFilePath, {
content: updatedFile,
timestamp: getFileModificationTime(absoluteFilePath),
})
6.7 FileReadTool 的安全防护
// 设备文件黑名单 — 防止读取无限流
const BLOCKED_DEVICE_PATHS = new Set([
'/dev/zero', '/dev/random', '/dev/urandom',
'/dev/stdin', '/dev/tty', '/dev/console',
])
// UNC 路径拦截 — 防止 NTLM 凭据泄漏
if (fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//')) {
return { result: true } // 静默跳过
}
// 文件读取去重 — 同一文件同一范围不重复读取
const existingState = readFileState.get(fullFilePath)
if (existingState && existingState.offset === offset && existingState.limit === limit) {
const mtimeMs = await getFileModificationTimeAsync(fullFilePath)
if (mtimeMs === existingState.timestamp) {
return { data: { type: 'file_unchanged' } } // 返回stub,不重发内容
}
}
6.8 GrepTool 的 Ripgrep 集成
// VCS 目录排除
const VCS_DIRECTORIES_TO_EXCLUDE = ['.git', '.svn', '.hg', '.bzr', '.jj', '.sl']
for (const dir of VCS_DIRECTORIES_TO_EXCLUDE) {
args.push('--glob', `!${dir}`)
}
args.push('--max-columns', '500') // 防止 base64/minified 内容膨胀
// 默认分页限制 — 防止上下文膨胀
const DEFAULT_HEAD_LIMIT = 250
// 负数 pattern 需要 -e 前缀,否则 rg 当作 flag 解析
if (pattern.startsWith('-')) {
args.push('-e', pattern)
}
6.9 ToolSearchTool:延迟加载机制
当工具总数超过阈值时,部分工具只发送 name,不发送完整 schema:
// ToolSearch 的 select 语法
// "select:ToolA,ToolB" — 精确选择
const selectMatch = query.match(/^select:(.+)$/i)
// 关键词搜索评分
if (parsed.parts.includes(term)) {
score += parsed.isMcp ? 12 : 10 // MCP 工具名额外加权
} else if (parsed.parts.some(part => part.includes(term))) {
score += parsed.isMcp ? 6 : 5
}
if (pattern.test(hintNormalized)) {
score += 4 // searchHint 匹配
}
if (pattern.test(descNormalized)) {
score += 2 // 描述匹配
}
6.10 并发执行策略
class StreamingToolExecutor {
async execute(toolUses, context) {
// Read/Grep/Glob 标记为 concurrencySafe → Promise.all() 并行
const safe = toolUses.filter(t => findTool(t).isConcurrencySafe())
const unsafe = toolUses.filter(t => !findTool(t).isConcurrencySafe())
const safeResults = await Promise.all(
safe.map(t => this.executeSingle(t, context))
)
// Bash/Write/Edit 等有副作用的工具 → 严格串行
const unsafeResults = []
for (const t of unsafe) {
unsafeResults.push(await this.executeSingle(t, context))
}
}
}
七、权限系统:6 层纵深防御
7.1 权限模式
const PERMISSION_MODE_CONFIG = {
default: { title: 'Default' }, // 交互式提示
plan: { title: 'Plan Mode' }, // 只读+规划,不执行
acceptEdits: { title: 'Accept edits' }, // 自动接受编辑
bypassPermissions: { title: 'Bypass' }, // 全部放行
dontAsk: { external: 'dontAsk' }, // 不提示
auto: { title: 'Auto mode' } // ML 分类器自动判断
}
7.2 完整检查链
用户提交工具调用(如 Bash: "rm -rf /")
│
▼
① validateInput() — 工具自定义校验
│ 例:FileEditTool 检查文件大小是否超过 1GiB
▼
② alwaysDenyRules 检查 — 黑名单直接拦截
│ 匹配: 工具名 + ruleContent (glob pattern)
│ 例:Deny "Bash(rm -rf *)"
▼
③ alwaysAllowRules 检查 — 白名单直接放行
│ 例:Allow "Bash(git *)"
▼
④ Auto Mode Classifier (Feature-gated: TRANSCRIPT_CLASSIFIER)
│ ML 模型分析整个对话上下文
│ 判断操作是否安全
│ ├→ 安全 → 放行
│ ├→ 危险 → 拒绝
│ └→ 不确定 → 继续到 ⑤
▼
⑤ PreToolUse Hook 检查
│ 执行用户定义的 Shell 脚本
│ exitCode=0 → 放行
│ exitCode=2 → 拦截
▼
⑥ 交互式提示 — 弹出终端 UI 询问用户
│ Allow / Deny / Always Allow
▼
canUseTool() — 最终裁决
7.3 拒绝追踪与降级
// 连续被拒绝 3 次后,从自动拒绝降级为弹窗询问
// 防止 AI 陷入 "尝试 → 被拒 → 再尝试" 的死循环
permissionDenials.push({ tool, input, reason })
if (denialCount >= 3) {
fallbackToPrompt = true // 弹窗让用户手动决定
}
7.4 危险路径检测
// utils/permissions/filesystem.ts (62KB)
export const DANGEROUS_FILES = [
'.gitconfig', '.bashrc', '.zshrc', '.mcp.json', '.claude.json'
]
export const DANGEROUS_DIRECTORIES = [
'.git', '.vscode', '.idea', '.claude'
]
// bypassPermissions 模式下,检测 root 权限 + 沙箱状态
if (process.getuid() === 0 && process.env.IS_SANDBOX !== '1') {
console.error('--dangerously-skip-permissions cannot be used with root/sudo')
process.exit(1)
}
八、Hook 系统:8 种事件类型
8.1 事件类型
type HookEvent =
| 'PreToolUse' // 工具执行前(可拦截)
| 'PostToolUse' // 工具执行后
| 'PostToolUseFailure' // 工具失败后
| 'PermissionDenied' // 权限拒绝
| 'Notification' // 通知时
| 'UserPromptSubmit' // 用户提交消息
| 'SessionStart' // 会话启动
| 'Stop' // Claude 停止前
| 'StopFailure' // 停止失败时
8.2 Hook 配置 Schema
Hook 支持 4 种执行方式(Zod 辨别联合类型):
const HookCommandSchema = z.discriminatedUnion('type', [
// ① Shell 命令
z.object({
type: z.literal('command'),
command: z.string(),
if: z.string().optional(), // 过滤条件
timeout: z.number().optional(),
once: z.boolean().optional(), // 只执行一次
async: z.boolean().optional(), // 异步执行
}),
// ② Prompt(交给 Claude 判断)
z.object({
type: z.literal('prompt'),
prompt: z.string(),
model: z.string().optional(),
}),
// ③ HTTP 调用
z.object({
type: z.literal('http'),
url: z.string().url(),
headers: z.record(z.string()).optional(),
}),
// ④ Agent(另起一个 Claude 验证)
z.object({
type: z.literal('agent'),
prompt: z.string(),
model: z.string().optional(),
}),
])
九、MCP 集成:5 种传输协议
9.1 配置 scope
MCP 配置支持 7 个层级,按优先级合并:
type ConfigScope =
| 'local' // 项目本地 (.mcp.json)
| 'user' // 用户级 (~/.claude/mcp.json)
| 'project' // 项目级 (.claude/mcp.json)
| 'dynamic' // 运行时动态添加
| 'enterprise' // 企业策略
| 'claudeai' // claude.ai 云端配置
| 'managed' // 远程管理
9.2 传输方式
type Transport = 'stdio' | 'sse' | 'sse-ide' | 'http' | 'ws' | 'sdk'
9.3 连接状态机
type MCPServerConnection =
| { type: 'connected', client: Client, capabilities, cleanup() }
| { type: 'failed', error: string }
| { type: 'needs-auth', authUrl: string } // 需要 OAuth
| { type: 'pending' } // 连接中
| { type: 'disabled' } // 已禁用
9.4 原子配置写入
MCP 配置文件写入使用原子 rename 模式:
async function writeMcpjsonFile(config) {
const tempPath = `${mcpJsonPath}.tmp.${process.pid}.${Date.now()}`
const handle = await open(tempPath, 'w', existingMode ?? 0o644)
try {
await handle.writeFile(JSON.stringify(config, null, 2))
await handle.datasync() // 强制刷盘
} finally {
await handle.close()
}
// 原子 rename
if (existingMode !== undefined) await chmod(tempPath, existingMode)
await rename(tempPath, mcpJsonPath)
}
十、终端 UI:深度 Fork 的 Ink
Claude Code 不是简单使用 Ink 库,而是 fork 并深度定制 了整个终端渲染引擎(50+ 文件):
ink/
├── root.ts # 渲染引擎(入口)
├── render-to-screen.ts # ANSI 序列生成 + diff 优化
├── layout/engine.ts # Yoga 布局引擎包装
├── dom.ts # 虚拟 DOM
├── frame.ts # 帧管理(FlickerReason 追踪)
├── focus.ts # 焦点管理
├── hit-test.ts # 鼠标点击测试
├── selection.ts # 文本选择
├── bidi.ts # 双向文本(RTL 支持)
├── wrap-text.ts # 文本换行
├── Ansi.tsx # ANSI 转义序列组件
├── events/
│ ├── click-event.ts # 鼠标事件
│ ├── input-event.ts # 键盘输入(含 Key 类型)
│ ├── terminal-focus-event.ts # 终端焦点
│ └── emitter.ts # 事件发射器
├── hooks/
│ ├── use-input.ts # 键盘输入 hook
│ ├── use-animation-frame.ts # 动画帧
│ ├── use-interval.ts # 定时器
│ ├── use-selection.ts # 文本选择
│ ├── use-tab-status.ts # Tab 状态 (OSC)
│ ├── use-terminal-viewport.ts # 视口感知
│ └── use-terminal-focus.ts # 终端焦点
└── components/
├── Box.tsx, Text.tsx # 基础组件
├── Button.tsx # 按钮(含 ButtonState)
├── Link.tsx # 终端超链接
├── AlternateScreen.tsx # 备用屏幕缓冲区
├── NoSelect.tsx # 不可选文本
└── RawAnsi.tsx # 原始 ANSI 输出
虚拟滚动
// hooks/useVirtualScroll.ts (35KB)
const DEFAULT_ESTIMATE = 3 // 未测量项的预估高度(故意偏低)
const OVERSCAN_ROWS = 80 // 视口外额外渲染行数
const SCROLL_QUANTUM = 40 // 重渲染阈值(半个 overscan)
const MAX_MOUNTED_ITEMS = 300 // 最大挂载项数
const SLIDE_STEP = 25 // 每次提交的最大新增项(防止 290ms 同步阻塞)
主题系统
// ink.ts — 全局主题注入
function withTheme(node: ReactNode): ReactNode {
return createElement(ThemeProvider, null, node)
}
export async function createRoot(options?: RenderOptions): Promise<Root> {
const root = await inkCreateRoot(options)
return {
...root,
render: node => root.render(withTheme(node)), // 包装每次渲染
}
}
十一、上下文管理:Memoized 的系统提示词
11.1 Git 状态收集
// context.ts
export const getGitStatus = memoize(async (): Promise<string | null> => {
// 并行执行所有 git 命令
const [branch, mainBranch, status, log, userName] = await Promise.all([
getBranch(),
getDefaultBranch(),
execFileNoThrow(gitExe(), ['--no-optional-locks', 'status', '--short']),
execFileNoThrow(gitExe(), ['--no-optional-locks', 'log', '--oneline', '-n', '5']),
execFileNoThrow(gitExe(), ['config', 'user.name']),
])
// 状态超过 2000 字符时截断
const truncatedStatus = status.length > MAX_STATUS_CHARS
? status.substring(0, MAX_STATUS_CHARS) +
'\n... (truncated. Run "git status" using BashTool for full output)'
: status
})
11.2 CCR 跳过 Git
export const getSystemContext = memoize(async () => {
// CCR 远程模式跳过 git 状态(不需要,也避免开销)
const gitStatus = isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)
? null
: await getGitStatus()
return {
...(gitStatus && { gitStatus }),
// Ant-only: cache breaker 注入
...(feature('BREAK_CACHE_COMMAND') && injection
? { cacheBreaker: `[CACHE_BREAKER: ${injection}]` }
: {}),
}
})
11.3 注入变更清除缓存
// 当系统提示词注入内容变化时,立即清除 memoize 缓存
export function setSystemPromptInjection(value: string | null): void {
systemPromptInjection = value
getUserContext.cache.clear?.()
getSystemContext.cache.clear?.()
}
十二、成本追踪:每模型粒度的会话管理
// cost-tracker.ts
type StoredCostState = {
totalCostUSD: number
totalAPIDuration: number
totalAPIDurationWithoutRetries: number
totalToolDuration: number
totalLinesAdded: number
totalLinesRemoved: number
modelUsage: { [modelName: string]: ModelUsage }
}
会话恢复
成本数据通过 sessionId 匹配恢复,防止不同会话的成本混淆:
export function getStoredSessionCosts(sessionId: string): StoredCostState | undefined {
const projectConfig = getCurrentProjectConfig()
// 只有 sessionId 匹配才恢复
if (projectConfig.lastSessionId !== sessionId) {
return undefined
}
return { totalCostUSD: projectConfig.lastCost ?? 0, ... }
}
Advisor 递归成本追踪
Claude Code 支持 "Advisor" 模式(另一个模型辅助当前模型),成本递归累加:
export function addToTotalSessionCost(cost, usage, model) {
// 递归追踪 advisor 用量
for (const advisorUsage of getAdvisorUsage(usage)) {
const advisorCost = calculateUSDCost(advisorUsage.model, advisorUsage)
logEvent('tengu_advisor_tool_token_usage', {
advisor_model: advisorUsage.model,
cost_usd_micros: Math.round(advisorCost * 1_000_000),
})
totalCost += addToTotalSessionCost(advisorCost, advisorUsage, advisorUsage.model)
}
}
十三、会话历史:JSONL + 反向读取
存储格式
type LogEntry = {
display: string // 显示文本
pastedContents: Record<number, StoredPastedContent> // 粘贴内容引用
timestamp: number
project: string // 项目路径
sessionId?: string
}
// 粘贴内容分流
type StoredPastedContent = {
id: number
type: 'text' | 'image'
content?: string // 小于 1024 字符 → 内联
contentHash?: string // 大于 1024 字符 → hash 引用,异步写入磁盘
}
反向读取(Up 键历史)
async function* makeLogEntryReader(): AsyncGenerator<LogEntry> {
// 1. 先 yield 未刷盘的 pending 条目
for (let i = pendingEntries.length - 1; i >= 0; i--) {
yield pendingEntries[i]!
}
// 2. 从磁盘反向读取 JSONL
for await (const line of readLinesReverse(historyPath)) {
const entry = deserializeLogEntry(line)
// 跳过已删除的条目
if (skippedTimestamps.has(entry.timestamp)) continue
yield entry
}
}
刷盘与文件锁
async function immediateFlushHistory() {
const historyPath = join(getClaudeConfigHomeDir(), 'history.jsonl')
// 文件锁(10s stale, 3 次重试)
release = await lock(historyPath, {
stale: 10000,
retries: { retries: 3, minTimeout: 50 },
})
// 批量追加
const jsonLines = pendingEntries.map(entry => jsonStringify(entry) + '\n')
pendingEntries = []
await appendFile(historyPath, jsonLines.join(''), { mode: 0o600 })
}
十四、Bridge:远程执行引擎
Bridge 让 claude.ai 网页端可以远程执行本地代码。
架构
claude.ai 网页 ←HTTP→ Anthropic API ←轮询→ Bridge 进程 ←spawn→ 本地 Claude Code
三种 Spawn 模式
type SpawnMode = 'single-session' | 'worktree' | 'same-dir'
| 模式 | 说明 | 适用场景 |
|---|---|---|
single-session | 一个目录一个会话,完成即销毁 | 一次性任务 |
worktree | 持久服务器,每个会话用 git worktree 隔离 | 多人协作 |
same-dir | 持久服务器,共享目录 | 简单场景(有干扰风险) |
WorkSecret 协议
type WorkSecret = {
version: number
session_ingress_token: string // JWT token
api_base_url: string
sources: Array<{ // Git 源
type: string
git_info?: { repo: string; ref?: string; token?: string }
}>
auth: Array<{ type: string; token: string }>
claude_code_args?: Record<string, string>
mcp_config?: unknown // MCP 服务器配置
environment_variables?: Record<string, string>
}
退避策略
const DEFAULT_BACKOFF = {
connInitialMs: 2_000, // 初始连接重试
connCapMs: 120_000, // 最大连接重试(2分钟)
connGiveUpMs: 600_000, // 放弃连接(10分钟)
generalInitialMs: 500,
generalCapMs: 30_000,
generalGiveUpMs: 600_000,
}
十五、Coordinator 模式:多智能体编排
Feature-gated 的多智能体模式(COORDINATOR_MODE):
// coordinator/coordinatorMode.ts
export function getCoordinatorSystemPrompt(): string {
return `You are Claude Code, an AI assistant that orchestrates
software engineering tasks across multiple workers.
## Your Role
You are a **coordinator**. Your job is to:
- Help the user achieve their goal
- Direct workers to research, implement and verify code changes
- Synthesize results and communicate with the user
- Answer questions directly when possible
## Your Tools
- **Agent** - Spawn a new worker
- **SendMessage** - Continue an existing worker
- **TaskStop** - Stop a running worker`
}
// Worker 工具集过滤(Simple 模式下只给 Bash/Read/Edit)
export function getCoordinatorUserContext(mcpClients, scratchpadDir?) {
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
}
十六、快捷键系统:和弦 + 平台自适应
平台检测
// keybindings/defaultBindings.ts
const IMAGE_PASTE_KEY = getPlatform() === 'windows' ? 'alt+v' : 'ctrl+v'
// VT 模式检测(影响 Shift+Tab 是否可用)
const SUPPORTS_TERMINAL_VT_MODE =
getPlatform() !== 'windows' ||
(isRunningWithBun()
? satisfies(process.versions.bun, '>=1.2.23')
: satisfies(process.versions.node, '>=22.17.0 <23.0.0 || >=24.2.0'))
const MODE_CYCLE_KEY = SUPPORTS_TERMINAL_VT_MODE ? 'shift+tab' : 'meta+m'
和弦(Chord)支持
// 例:ctrl+x ctrl+k → 杀死所有 Agent
export const DEFAULT_BINDINGS = [
{
context: 'Chat',
bindings: {
'ctrl+x ctrl+k': 'chat:killAgents', // 两键和弦
'ctrl+x ctrl+e': 'chat:externalEditor',
},
},
]
十七、Vim 模式:完整的状态机
// vim/types.ts — 状态机定义
export type VimState =
| { mode: 'INSERT'; insertedText: string }
| { mode: 'NORMAL'; command: CommandState }
export type CommandState =
| { type: 'idle' } // 等待输入
| { type: 'count'; digits: string } // 数字前缀 (3dd)
| { type: 'operator'; op: Operator; count: number } // d/c/y 等待 motion
| { type: 'operatorCount'; op: Operator; count: number; digits: string }
| { type: 'operatorFind'; op: Operator; count: number; find: FindType }
| { type: 'operatorTextObj'; op: Operator; count: number; scope: 'inner'|'around' }
| { type: 'find'; find: 'f'|'F'|'t'|'T'; count: number }
| { type: 'g'; count: number } // gg/G
| { type: 'replace'; count: number } // r 等待字符
| { type: 'indent'; dir: '>'|'<'; count: number } // >> / <<
支持 dot-repeat (.)、register ("a)、text objects (ciw, da") 等完整 Vim 操作。
十八、彩蛋:Buddy 宠物系统
// buddy/types.ts
export const SPECIES = [
'duck', 'goose', 'blob', 'cat', 'dragon', 'octopus', 'owl', 'penguin',
'turtle', 'snail', 'ghost', 'axolotl', 'capybara', 'cactus',
'robot', 'rabbit', 'mushroom', 'chonk',
] as const // 18 种物种
export const RARITIES = ['common', 'uncommon', 'rare', 'epic', 'legendary'] as const
export type CompanionBones = {
rarity: Rarity
species: Species
eye: Eye
hat: Hat
shiny: boolean // 闪光版
stats: Record<StatName, number> // Debugging, Patience, Chaos, Wisdom, Snark
}
export type CompanionSoul = {
name: string // AI 生成的名字
personality: string // AI 生成的性格
}
用 hash(userId) 确定性生成,保证同一用户在所有会话中看到同一只宠物。首次 "孵化" 时由 Claude 生成名字和性格。还有一个 CompanionSprite.tsx (45KB) 实现了帧动画。
十九、Feature Gate:编译时 vs 运行时
Claude Code 使用两层特性开关:
编译时(Bun DCE)
import { feature } from 'bun:bundle';
// 编译时完全消除 — 外部构建中这段代码不存在
if (feature('COORDINATOR_MODE')) {
const module = require('./coordinatorMode.js')
}
已发现的 Feature Gate:
| Flag | 说明 |
|---|---|
COORDINATOR_MODE | 多智能体编排 |
VOICE_MODE | 语音输入 |
HISTORY_SNIP | 历史截断压缩 |
TRANSCRIPT_CLASSIFIER | ML 权限分类器 |
REACTIVE_COMPACT | 主动上下文压缩 |
CONTEXT_COLLAPSE | 语义上下文折叠 |
KAIROS | Assistant 模式 |
BRIDGE_MODE | 远程桥接 |
PROACTIVE | 主动提示 |
AGENT_TRIGGERS | Cron 触发器 |
UDS_INBOX | Unix Socket 收件箱 |
DAEMON | 守护进程模式 |
ABLATION_BASELINE | 消融实验基线 |
DUMP_SYSTEM_PROMPT | 导出系统提示词 |
WORKFLOW_SCRIPTS | 工作流脚本 |
WEB_BROWSER_TOOL | 浏览器工具 |
TERMINAL_PANEL | 终端面板 |
EXPERIMENTAL_SKILL_SEARCH | 技能搜索 |
KAIROS_GITHUB_WEBHOOKS | GitHub Webhook 订阅 |
CHICAGO_MCP | Computer Use MCP |
OVERFLOW_TEST_TOOL | 溢出测试 |
运行时(GrowthBook)
// 基于用户/环境动态开关
const isEnabled = getFeatureValue_CACHED_MAY_BE_STALE('tengu_some_feature', false)
用户类型区分
// 编译时字符串替换
if ("external" !== 'ant' && isBeingDebugged()) {
process.exit(1) // 外部构建禁止调试
}
// 运行时环境变量
if (process.env.USER_TYPE === 'ant') {
// Anthropic 内部工具:ConfigTool, TungstenTool, REPLTool
}
二十、配额限制与"封号"机制
很多人关心:Claude Code 会不会封号?源码里有什么限制机制?
20.1 没有传统"封号",但有严格的配额执行
Claude Code 不在客户端做任何封号判断。所有限制通过 API 响应头 anthropic-ratelimit-unified-* 由服务端下发,客户端只是忠实执行。
// services/claudeAiLimits.ts
type QuotaStatus = 'allowed' | 'allowed_warning' | 'rejected'
type RateLimitType =
| 'five_hour' // 5 小时滑动窗口
| 'seven_day' // 7 天窗口
| 'seven_day_opus' // Opus 专用 7 天限制
| 'seven_day_sonnet' // Sonnet 专用 7 天限制
| 'overage' // 超额使用(付费后仍有上限)
20.2 服务端响应头解析
每次 API 调用后,客户端解析以下响应头:
function computeNewLimitsFromHeaders(headers: Headers): ClaudeAILimits {
// 核心状态:allowed / allowed_warning / rejected
const status = headers.get('anthropic-ratelimit-unified-status') as QuotaStatus || 'allowed'
// 重置时间(Unix 秒)
const resetsAt = Number(headers.get('anthropic-ratelimit-unified-reset'))
// 命中的限制类型
const rateLimitType = headers.get('anthropic-ratelimit-unified-representative-claim')
// 超额使用状态
const overageStatus = headers.get('anthropic-ratelimit-unified-overage-status')
// 超额被禁用的原因(12 种可能)
const overageDisabledReason = headers.get(
'anthropic-ratelimit-unified-overage-disabled-reason'
)
// 使用率(0~1)
const utilization5h = Number(headers.get('anthropic-ratelimit-unified-5h-utilization'))
const utilization7d = Number(headers.get('anthropic-ratelimit-unified-7d-utilization'))
}
当 status === 'rejected' 时,所有 API 调用立即被阻止,用户必须等到 resetsAt 时间。
20.3 超额禁用原因(12 种)
type OverageDisabledReason =
| 'overage_not_provisioned' // 未开通超额
| 'org_level_disabled' // 组织级别禁用
| 'org_level_disabled_until' // 组织级别临时禁用
| 'out_of_credits' // 余额不足
| 'seat_tier_level_disabled' // 席位等级禁用
| 'member_level_disabled' // 个人账户禁用
| 'seat_tier_zero_credit_limit' // 席位等级信用额度为零
| 'group_zero_credit_limit' // 组信用额度为零
| 'member_zero_credit_limit' // 个人信用额度为零
| 'org_service_level_disabled' // 组织服务级别禁用
| 'org_service_zero_credit_limit'// 组织服务信用额度为零
| 'no_limits_configured' // 未配置限制
| 'unknown'
20.4 预飞检查(Pre-flight)
每次会话启动时,Claude Code 发一个最小请求来探测配额状态:
async function makeTestQuery() {
const model = getSmallFastModel() // 用最小模型
const anthropic = await getAnthropicClient({
maxRetries: 0,
model,
source: 'quota_check',
})
return anthropic.beta.messages.create({
model,
max_tokens: 1, // 只请求 1 个 token
messages: [{ role: 'user', content: 'quota' }],
}).asResponse()
}
用最便宜的模型、最少的 token 做探测,只为拿到响应头中的配额信息。
20.5 早期预警系统
Claude Code 会预判你的消耗速度,在你还没被限制时就发出警告:
const EARLY_WARNING_CONFIGS = [
{
rateLimitType: 'five_hour',
windowSeconds: 5 * 60 * 60,
thresholds: [
{ utilization: 0.9, timePct: 0.72 }, // 用了90%但时间只过72%
],
},
{
rateLimitType: 'seven_day',
windowSeconds: 7 * 24 * 60 * 60,
thresholds: [
{ utilization: 0.75, timePct: 0.6 }, // 用了75%但时间只过60%
{ utilization: 0.5, timePct: 0.35 }, // 用了50%但时间只过35%
{ utilization: 0.25, timePct: 0.15 }, // 用了25%但时间只过15%
],
},
]
翻译一下:如果你在 7 天窗口的前 35% 时间里就用掉了 50% 的配额,系统会警告你正在 "燃烧" 配额。
20.6 429 错误处理
收到 HTTP 429 时,状态被强制设为 rejected:
export function extractQuotaStatusFromError(error: APIError): void {
if (error.status !== 429) return
let newLimits = { ...currentLimits }
if (error.headers) {
newLimits = computeNewLimitsFromHeaders(error.headers)
}
// 无论 headers 是否存在,429 = 立刻 rejected
newLimits.status = 'rejected'
emitStatusChange(newLimits)
}
20.7 用户看到的限制信息
// 实际展示给用户的消息
"You've hit your session limit · resets in 4 hours"
"You've hit your weekly limit"
"You've hit your Opus limit"
"You're out of extra usage"
20.8 反调试机制
外部构建中,检测到 Node.js Inspector 活跃时直接退出:
// 检测 --inspect、--inspect-brk、--debug、--debug-brk
// 以及 inspector.url() 是否活跃
if ("external" !== 'ant' && isBeingDebugged()) {
process.exit(1) // 静默退出,无任何错误信息
}
"external" 是编译时替换的字符串——Anthropic 内部构建替换为 'ant',外部构建保持 'external'。所以只有外部用户被拦截调试。
20.9 客户端无法绕过
源码中没有任何绕过机制:
- 不能伪造响应头
- 不能修改重置时间
- 不能欺骗授权
- 所有限制执行都是服务端驱动的
- 429 错误是 fatal,不可重试(超过限制后)
结论:Claude Code 的限制完全是服务端控制的。客户端只是一个忠实的执行者,没有任何后门或绕过逻辑。
二十一、数据收集与遥测:你的数据去了哪里
这是很多开发者关心的问题。源码揭示了完整的遥测架构。
21.1 三大数据出口
Claude Code CLI
│
├─ logEvent() → 事件队列
│ ├──→ Datadog (30+ 种事件)
│ │ POST https://http-intake.logs.us5.datadoghq.com/api/v2/logs
│ │ Token: pubbbf48e6d78dae54bceaa4acf463299bf
│ │
│ └──→ Anthropic 1P (完整事件+用户元数据)
│ POST https://api.anthropic.com/api/event_logging/batch
│ 失败时持久化到 ~/.claude/telemetry/1p_failed_events.*.json
│
└─ Metrics → BigQuery
POST https://api.anthropic.com/api/claude_code/metrics
21.2 发送到 Datadog 的事件(30+ 种)
API 相关: tengu_api_error, tengu_api_success
认证相关: tengu_oauth_success, tengu_oauth_error, token refresh 事件
工具使用: tengu_tool_use_success, tengu_tool_use_error
会话相关: tengu_session_file_read, tengu_exit, tengu_init
语音相关: tengu_voice_toggled, tengu_voice_recording_started
Chrome: chrome_bridge_connection_succeeded, chrome_bridge_tool_call_completed
团队同步: tengu_team_mem_sync_pull, tengu_team_mem_sync_push
配额变化: tengu_claudeai_limits_status_changed
21.3 收集的用户数据
每个事件附带的元数据:
// firstPartyEventLogger.ts — 1P 事件元数据
{
event_id: UUID,
event_name: '事件名',
client_timestamp: '时间戳',
device_id: '持久化设备ID', // getOrCreateUserID()
user_id: '同 device_id',
email: '用户邮箱', // OAuth 登录后可用
session_id: '会话ID',
core_metadata: {
service_name: 'claude-code',
version: '版本号',
platform: 'darwin/linux/win32',
os_info: 'OS版本'
},
user_metadata: {
organization_uuid: '组织ID',
account_uuid: '账户ID',
subscription_type: '订阅类型',
rate_limit_tier: 'API限制等级',
first_token_time: '首次使用时间'
},
auth: {
account_uuid: '账户UUID',
organization_uuid: '组织UUID'
}
}
21.4 GitHub CI 环境下的额外收集
当在 GitHub Actions 中运行时,额外收集:
// GrowthBook 用户属性
{
github: {
actor: 'GitHub 用户名',
actorId: 'GitHub 用户ID',
repository: '仓库名',
repositoryId: '仓库ID',
repositoryOwner: '仓库所有者',
repositoryOwnerId: '所有者ID'
}
}
21.5 代码内容保护
源码中有显式的代码泄漏防护:
// 元数据类型名本身就是防线——类型名强制开发者确认
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = string
// metadata.ts — 截断和清洗规则
- 字符串截断到 512 字符(只展示前 128 字符)
- JSON 输出上限 4KB
- 集合元素上限 20 个
- 嵌套深度上限 2 层
- 以 _ 开头的内部字段自动剥离
// 用户提示词默认 redacted
userPrompt = '<REDACTED>' // 除非显式开启 OTEL_LOG_USER_PROMPTS=1
// MCP 工具名默认匿名化
mcpToolName = 'mcp_tool' // 除非是官方 MCP 注册表中的工具
21.6 用户桶哈希
为了在 Datadog 中统计独立用户数同时保护隐私:
// datadog.ts — 用户 ID 哈希到 30 个桶
function getUserBucket(userId: string): number {
const hash = SHA256(userId)
return hash % 30 // 30 个桶,无法反推用户 ID
}
21.7 关闭遥测
Claude Code 提供了两个级别的关闭方式:
# 级别 1:关闭遥测(Datadog + 1P 事件 + 反馈调查)
export DISABLE_TELEMETRY=1
# 级别 2:关闭所有非必要网络请求(最彻底)
# 包括:遥测、自动更新、release notes、模型能力刷新
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
此外,组织管理员可以通过 API 在组织级别禁用指标收集:
GET /api/claude_code/organizations/metrics_enabled → false
21.8 事件采样
并非每个事件都会被发送——GrowthBook 配置了采样率:
// firstPartyEventLogger.ts
// tengu_event_sampling_config 配置每种事件的采样率
// 如果被采样掉,sample_rate 字段会被添加到元数据中
21.9 失败事件持久化
如果事件发送失败,会写入本地磁盘:
~/.claude/telemetry/1p_failed_events.<sessionId>.<uuid>.json
下次启动时重试发送(二次退避,最多 8 次尝试,最大间隔 30 秒)。
21.10 遥测小结
| 类别 | 收集内容 | 是否包含代码 |
|---|---|---|
| 身份信息 | deviceId, email, accountUuid, orgUuid | 否 |
| 使用数据 | 模型选择, token 计数, 成本, 工具使用 | 否 |
| 性能数据 | API 延迟, 启动时间, FPS | 否 |
| 错误数据 | 错误类型, HTTP 状态码 | 否 |
| 代码内容 | N/A | 显式阻止 |
| 用户提示词 | 默认 <REDACTED> | 默认否 |
| 文件路径 | 工作区路径(非文件路径) | 工作区级别 |
结论:Claude Code 收集的是使用行为数据,不是代码内容。有明确的类型系统防止开发者意外发送代码。可以通过环境变量完全关闭。
二十二、设计哲学总结
通读 1884 个文件后,总结 Claude Code 的 8 条设计原则:
1. 启动性能是一等公民
- 快速路径 + 动态 import + 并行预取 + 编译时 DCE
profileCheckpoint()在每个分支点标记时间- MDM + Keychain 读取在 import 副作用中并行启动
2. 安全性纵深防御
- 6 层权限检查 + Hook 拦截 + ML 分类器
- 反调试机制(外部构建检测 Inspector 直接退出)
- UNC 路径拦截防止 NTLM 凭据泄漏
- 设备文件黑名单防止无限读取
- 原子写入防止竞态条件
3. 流式一切
AsyncGenerator贯穿整个消息流- 工具执行流式上报 progress
- 历史读取用异步生成器反向遍历
- REPL 渲染异步于查询执行
4. 状态管理极简
- 34 行自研 Store,
Object.is()变更检测 - 不引入任何第三方状态库
useSyncExternalStore对接 React
5. 可扩展优先
- 43 个工具通过统一接口注册
- MCP 让外部工具无缝接入(5 种传输协议)
- Hook 系统让用户自定义行为(4 种执行方式)
- 技能系统让社区贡献能力
6. 渐进式发布
- 20+ 个 Feature Gate,编译时消除未启用代码
- GrowthBook 运行时灰度
USER_TYPE区分内部/外部构建
7. 上下文即生命线
- Memoized 系统上下文(一次对话只计算一次)
- 三种压缩策略应对上下文窗口限制
- 文件读取 LRU 缓存 + 去重
- 工具结果预算控制
- ToolSearch 延迟加载省 token
8. 测试友好
- 依赖注入贯穿核心模块
QueryDeps允许注入 mock- Feature Gate 在
bun test下返回 false TestingPermissionTool仅测试环境启用
声明:本文仅用于技术学习和架构分析。源码分析基于公开泄漏的代码,所有商标和版权归 Anthropic 所有。
如果觉得有收获,欢迎点赞收藏。关于 Claude Code 的架构细节,欢迎评论区交流。