Claude Code 源码架构深度解析:1884 个文件背后的 AI 编程工具设计哲学

0 阅读24分钟

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 Hooks85 个
服务模块35+ 个
技术栈TypeScript + React + Ink (终端 UI) + Zod + Bun

二、整体架构:五层设计

先上全局架构图,后面逐层拆解:

architecture_overview.svg

startup_flow (1).svg

query_dataflow.svg

三、入口层:毫秒级的启动优化

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(只读递归类型),但 tasksmcp 是可变的——因为它们需要高频更新。


五、查询引擎: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 CompactREACTIVE_COMPACT主动式压缩,更激进
History SnipHISTORY_SNIP直接截断历史,仅保留最近消息
Context CollapseCONTEXT_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_CLASSIFIERML 权限分类器
REACTIVE_COMPACT主动上下文压缩
CONTEXT_COLLAPSE语义上下文折叠
KAIROSAssistant 模式
BRIDGE_MODE远程桥接
PROACTIVE主动提示
AGENT_TRIGGERSCron 触发器
UDS_INBOXUnix Socket 收件箱
DAEMON守护进程模式
ABLATION_BASELINE消融实验基线
DUMP_SYSTEM_PROMPT导出系统提示词
WORKFLOW_SCRIPTS工作流脚本
WEB_BROWSER_TOOL浏览器工具
TERMINAL_PANEL终端面板
EXPERIMENTAL_SKILL_SEARCH技能搜索
KAIROS_GITHUB_WEBHOOKSGitHub Webhook 订阅
CHICAGO_MCPComputer 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 的架构细节,欢迎评论区交流。