Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

entrypoints/cli.tsx 是 Claude Code 二进制的第一段 TypeScript:在加载 main.tsx 之前处理 --version、daemon worker、bridge、bg sessions、MCP sidecar 等 fast-path。init.ts 则是 memoize 的全局初始化(配置、telemetry、网络 agent)。本章要求你能画出「用户执行 claude → 哪条分支 → 是否调用 init()」的决策树。

学完本章你应该能

  • 解释为何 ABLATION_BASELINE 必须在 cli.tsx 而非 init.ts
  • 列举至少 5 条 cli fast-path 及其 dynamic import 目标
  • 描述 init.ts 中 enableConfigs → safe env → telemetry 延迟加载顺序
  • 理解 startCapturingEarlyInput 与 main import 的时序
  • 知道 init 失败时 showInvalidConfigDialog 的动态 import 策略

核心概念(先读懂这些)

cli.tsx 设计目标:最小化冷启动模块评估

--version 路径零 dynamic import(除 MACRO.VERSION 内联)。其他路径先 import startupProfiler,再按 argv 分支 lazy load。BashTool/AgentTool 在 module eval 时捕获 DISABLE_BACKGROUND_TASKS,所以 ablation env 必须在 cli 顶层设置,init() 太晚。

init 与 cli 的职责分离

cli 只做 argv 路由;init 做一次性全局副作用(configs、graceful shutdown、OAuth、GrowthBook 相关 promise)。Agent SDK 测试可 mock init 而不经过 cli。init 使用 memoize 保证重复调用 no-op。

建议学习步骤

  1. 阅读 cli 顶层 env 与 ablation 块(源码块 A)
  2. 阅读 --version 与 cli_entry checkpoint(源码块 B)
  3. 阅读 bridge/daemon/bg 代表 fast-path(源码块 C)
  4. 阅读 fallthrough 到 main.tsx(源码块 D)
  5. 阅读 init.ts enableConfigs 与延迟 telemetry(源码块 E、F)

常见误区

注意

feature() 必须 inline 在 if 条件里,否则 DCE 无法剔除 ant-only 代码

注意

templates 路径用 process.exit(0) 而非 return——Ink TUI 可能残留 event loop handle

注意

init 里 React 的 showInvalidConfigDialog 仅在 ConfigParseError 路径 dynamic import

cli.tsx 在进程中的位置

npm bin 字段指向编译后的 cli.js,其实质 re-export cli.tsx::main()。

cli.tsx main()
  │
  ├─ [0 imports path] --version / -v / -V
  │
  ├─ profileCheckpoint('cli_entry')
  │
  ├─ ant-only: --dump-system-prompt
  ├─ sidecar: --claude-in-chrome-mcp, --chrome-native-host, --computer-use-mcp
  ├─ internal: --daemon-worker
  ├─ bridge: remote-control / rc / remote / sync / bridge
  ├─ daemon subcommand
  ├─ bg: ps / logs / attach / kill / --bg
  ├─ templates: new / list / reply
  ├─ environment-runner / self-hosted-runner
  ├─ tmux+worktree fast-path
  ├─ --update redirect → argv rewrite
  ├─ --bare → CLAUDE_CODE_SIMPLE=1
  │
  └─ startCapturingEarlyInput()
       → import('../main.js') → cliMain()

main.tsx 内再调用 init()、解析 Commander、最终 launchRepl 或 headless query。

顶层 side effects:env 与 ablation

cli 文件顶部 故意 保留 top-level side effects(eslint 豁免):

  1. COREPACK_ENABLE_AUTO_PIN = '0' — 防止 corepack 污染 package.json
  2. CLAUDE_CODE_REMOTE 时追加 NODE_OPTIONS --max-old-space-size=8192
  3. ABLATION_BASELINE feature + env 时批量设置 SIMPLE、DISABLE_THINKING、DISABLE_COMPACT 等

第三点注释强调:BashTool/AgentTool/PowerShellTool 在 import 时 读取 env 到 module-level const,init() 运行太晚。

CCR 容器 16GB 内存场景通过 REMOTE 分支限制 heap,避免 OOM。

源码引用: src/entrypoints/cli.tsx · 第 1–26 行(共 308 行)

   1| import { feature } from 'bun:bundle'
   2| 
   3| // Bugfix for corepack auto-pinning, which adds yarnpkg to peoples' package.jsons
   4| // eslint-disable-next-line custom-rules/no-top-level-side-effects
   5| process.env.COREPACK_ENABLE_AUTO_PIN = '0'
   6| 
   7| // Set max heap size for child processes in CCR environments (containers have 16GB)
   8| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level, custom-rules/safe-env-boolean-check
   9| if (process.env.CLAUDE_CODE_REMOTE === 'true') {
  10|   // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  11|   const existing = process.env.NODE_OPTIONS || ''
  12|   // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  13|   process.env.NODE_OPTIONS = existing
  14|     ? `${existing} --max-old-space-size=8192`
  15|     : '--max-old-space-size=8192'
  16| }
  17| 
  18| // Harness-science L0 ablation baseline. Inlined here (not init.ts) because
  19| // BashTool/AgentTool/PowerShellTool capture DISABLE_BACKGROUND_TASKS into
  20| // module-level consts at import time — init() runs too late. feature() gate
  21| // DCEs this entire block from external builds.
  22| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  23| if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  24|   for (const k of [
  25|     'CLAUDE_CODE_SIMPLE',
  26|     'CLAUDE_CODE_DISABLE_THINKING',

源码引用: src/entrypoints/cli.tsx · 第 16–26 行(共 308 行)

  16| }
  17| 
  18| // Harness-science L0 ablation baseline. Inlined here (not init.ts) because
  19| // BashTool/AgentTool/PowerShellTool capture DISABLE_BACKGROUND_TASKS into
  20| // module-level consts at import time — init() runs too late. feature() gate
  21| // DCEs this entire block from external builds.
  22| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  23| if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  24|   for (const k of [
  25|     'CLAUDE_CODE_SIMPLE',
  26|     'CLAUDE_CODE_DISABLE_THINKING',

--version 零加载快路径

args.length === 1 && (--version|-v|-V) 时:

  • 直接 console.log(${MACRO.VERSION} (Claude Code))
  • return,不 import startupProfiler、不 touch main

这是 packaging 与 CI 探测版本的最热路径,必须保持无依赖。MACRO.VERSION 由 Bun bundle 内联。

对比:下一条路径才 await import('../utils/startupProfiler.js') 并 profileCheckpoint('cli_entry')。

源码引用: src/entrypoints/cli.tsx · 第 28–48 行(共 308 行)

  28|     'DISABLE_COMPACT',
  29|     'DISABLE_AUTO_COMPACT',
  30|     'CLAUDE_CODE_DISABLE_AUTO_MEMORY',
  31|     'CLAUDE_CODE_DISABLE_BACKGROUND_TASKS',
  32|   ]) {
  33|     // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  34|     process.env[k] ??= '1'
  35|   }
  36| }
  37| 
  38| /**
  39|  * Bootstrap entrypoint - checks for special flags before loading the full CLI.
  40|  * All imports are dynamic to minimize module evaluation for fast paths.
  41|  * Fast-path for --version has zero imports beyond this file.
  42|  */
  43| async function main(): Promise<void> {
  44|   const args = process.argv.slice(2)
  45| 
  46|   // Fast-path for --version/-v: zero module loading needed
  47|   if (
  48|     args.length === 1 &&

源码引用: src/entrypoints/cli.tsx · 第 33–42 行(共 308 行)

  33|     // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
  34|     process.env[k] ??= '1'
  35|   }
  36| }
  37| 
  38| /**
  39|  * Bootstrap entrypoint - checks for special flags before loading the full CLI.
  40|  * All imports are dynamic to minimize module evaluation for fast paths.
  41|  * Fast-path for --version has zero imports beyond this file.
  42|  */

代表 fast-path:bridge 与 bg

bridge 路径(feature BRIDGE_MODE):

  • enableConfigs
  • OAuth accessToken 检查 先于 GrowthBook gate(无 auth 则 GB 无 user context)
  • waitForPolicyLimitsToLoad + allow_remote_control
  • bridgeMain(args.slice(1))

bg 路径(feature BG_SESSIONS):

  • ps / logs / attach / kill / --bg / --background
  • enableConfigs → dynamic import cli/bg.js
  • switch 分发 handler

两者均 不 import main.tsx,避免 Ink/React 进入内存。

源码引用: src/entrypoints/cli.tsx · 第 108–162 行(共 308 行)

 108|   // it calls them inside its run() fn.
 109|   if (feature('DAEMON') && args[0] === '--daemon-worker') {
 110|     const { runDaemonWorker } = await import('../daemon/workerRegistry.js')
 111|     await runDaemonWorker(args[1])
 112|     return
 113|   }
 114| 
 115|   // Fast-path for `claude remote-control` (also accepts legacy `claude remote` / `claude sync` / `claude bridge`):
 116|   // serve local machine as bridge environment.
 117|   // feature() must stay inline for build-time dead code elimination;
 118|   // isBridgeEnabled() checks the runtime GrowthBook gate.
 119|   if (
 120|     feature('BRIDGE_MODE') &&
 121|     (args[0] === 'remote-control' ||
 122|       args[0] === 'rc' ||
 123|       args[0] === 'remote' ||
 124|       args[0] === 'sync' ||
 125|       args[0] === 'bridge')
 126|   ) {
 127|     profileCheckpoint('cli_bridge_path')
 128|     const { enableConfigs } = await import('../utils/config.js')
 129|     enableConfigs()
 130| 
 131|     const { getBridgeDisabledReason, checkBridgeMinVersion } = await import(
 132|       '../bridge/bridgeEnabled.js'
 133|     )
 134|     const { BRIDGE_LOGIN_ERROR } = await import('../bridge/types.js')
 135|     const { bridgeMain } = await import('../bridge/bridgeMain.js')
 136|     const { exitWithError } = await import('../utils/process.js')
 137| 
 138|     // Auth check must come before the GrowthBook gate check — without auth,
 139|     // GrowthBook has no user context and would return a stale/default false.
 140|     // getBridgeDisabledReason awaits GB init, so the returned value is fresh
 141|     // (not the stale disk cache), but init still needs auth headers to work.
 142|     const { getClaudeAIOAuthTokens } = await import('../utils/auth.js')
 143|     if (!getClaudeAIOAuthTokens()?.accessToken) {
 144|       exitWithError(BRIDGE_LOGIN_ERROR)
 145|     }
 146|     const disabledReason = await getBridgeDisabledReason()
 147|     if (disabledReason) {
 148|       exitWithError(`Error: ${disabledReason}`)
 149|     }
 150|     const versionError = checkBridgeMinVersion()
 151|     if (versionError) {
 152|       exitWithError(versionError)
 153|     }
 154| 
 155|     // Bridge is a remote control feature - check policy limits
 156|     const { waitForPolicyLimitsToLoad, isPolicyAllowed } = await import(
 157|       '../services/policyLimits/index.js'
 158|     )
 159|     await waitForPolicyLimitsToLoad()
 160|     if (!isPolicyAllowed('allow_remote_control')) {
 161|       exitWithError(
 162|         "Error: Remote Control is disabled by your organization's policy.",

源码引用: src/entrypoints/cli.tsx · 第 182–209 行(共 308 行)

 182|   // Fast-path for `claude ps|logs|attach|kill` and `--bg`/`--background`.
 183|   // Session management against the ~/.claude/sessions/ registry. Flag
 184|   // literals are inlined so bg.js only loads when actually dispatching.
 185|   if (
 186|     feature('BG_SESSIONS') &&
 187|     (args[0] === 'ps' ||
 188|       args[0] === 'logs' ||
 189|       args[0] === 'attach' ||
 190|       args[0] === 'kill' ||
 191|       args.includes('--bg') ||
 192|       args.includes('--background'))
 193|   ) {
 194|     profileCheckpoint('cli_bg_path')
 195|     const { enableConfigs } = await import('../utils/config.js')
 196|     enableConfigs()
 197|     const bg = await import('../cli/bg.js')
 198|     switch (args[0]) {
 199|       case 'ps':
 200|         await bg.psHandler(args.slice(1))
 201|         break
 202|       case 'logs':
 203|         await bg.logsHandler(args[1])
 204|         break
 205|       case 'attach':
 206|         await bg.attachHandler(args[1])
 207|         break
 208|       case 'kill':
 209|         await bg.killHandler(args[1])

Fallthrough:main.tsx 与 early input

无 fast-path 匹配时:

startCapturingEarlyInput()
profileCheckpoint('cli_before_main_import')
const { main: cliMain } = await import('../main.js')
profileCheckpoint('cli_after_main_import')
await cliMain()
profileCheckpoint('cli_after_main_complete')

startCapturingEarlyInput 在用户敲键与 main 模块解析并行时缓冲 stdin,减少 REPL 首屏丢键。

--bare 在 fallthrough 前设置 CLAUDE_CODE_SIMPLE=1,使 Commander 构建选项时即生效,而非仅在 action handler 内。

void main() 顶层启动 async main,eslint 允许 top-level side effect。

源码引用: src/entrypoints/cli.tsx · 第 276–302 行(共 308 行)

 276|         const { exitWithError } = await import('../utils/process.js')
 277|         exitWithError(result.error)
 278|       }
 279|     }
 280|   }
 281| 
 282|   // Redirect common update flag mistakes to the update subcommand
 283|   if (
 284|     args.length === 1 &&
 285|     (args[0] === '--update' || args[0] === '--upgrade')
 286|   ) {
 287|     process.argv = [process.argv[0]!, process.argv[1]!, 'update']
 288|   }
 289| 
 290|   // --bare: set SIMPLE early so gates fire during module eval / commander
 291|   // option building (not just inside the action handler).
 292|   if (args.includes('--bare')) {
 293|     process.env.CLAUDE_CODE_SIMPLE = '1'
 294|   }
 295| 
 296|   // No special flags detected, load and run the full CLI
 297|   const { startCapturingEarlyInput } = await import('../utils/earlyInput.js')
 298|   startCapturingEarlyInput()
 299|   profileCheckpoint('cli_before_main_import')
 300|   const { main: cliMain } = await import('../main.js')
 301|   profileCheckpoint('cli_after_main_import')
 302|   await cliMain()

源码引用: src/entrypoints/cli.tsx · 第 281–285 行(共 308 行)

 281| 
 282|   // Redirect common update flag mistakes to the update subcommand
 283|   if (
 284|     args.length === 1 &&
 285|     (args[0] === '--update' || args[0] === '--upgrade')

init.ts:memoize 与 configs

export const init = memoize(async (): Promise&lt;void&gt; =&gt; { ... })

启动序列要点:

  1. enableConfigs() — 校验 settings,启用配置系统
  2. applySafeConfigEnvironmentVariables() — 信任对话框前仅应用安全 env
  3. applyExtraCACertsFromConfig() — TLS 握手前注入 CA
  4. setupGracefulShutdown()
  5. 延迟 dynamic import firstPartyEventLogger + growthbook
  6. populateOAuthAccountInfoIfNeeded()
  7. remote managed settings / policy limits promise 初始化
  8. configureGlobalMTLS() / configureGlobalAgents() / preconnectAnthropicApi()

注释说明 telemetry OpenTelemetry ~400KB 延迟到 setMeterState,gRPC exporter ~700KB 更晚。

源码引用: src/entrypoints/init.ts · 第 57–84 行(共 341 行)

  57| export const init = memoize(async (): Promise<void> => {
  58|   const initStartTime = Date.now()
  59|   logForDiagnosticsNoPII('info', 'init_started')
  60|   profileCheckpoint('init_function_start')
  61| 
  62|   // Validate configs are valid and enable configuration system
  63|   try {
  64|     const configsStart = Date.now()
  65|     enableConfigs()
  66|     logForDiagnosticsNoPII('info', 'init_configs_enabled', {
  67|       duration_ms: Date.now() - configsStart,
  68|     })
  69|     profileCheckpoint('init_configs_enabled')
  70| 
  71|     // Apply only safe environment variables before trust dialog
  72|     // Full environment variables are applied after trust is established
  73|     const envVarsStart = Date.now()
  74|     applySafeConfigEnvironmentVariables()
  75| 
  76|     // Apply NODE_EXTRA_CA_CERTS from settings.json to process.env early,
  77|     // before any TLS connections. Bun caches the TLS cert store at boot
  78|     // via BoringSSL, so this must happen before the first TLS handshake.
  79|     applyExtraCACertsFromConfig()
  80| 
  81|     logForDiagnosticsNoPII('info', 'init_safe_env_vars_applied', {
  82|       duration_ms: Date.now() - envVarsStart,
  83|     })
  84|     profileCheckpoint('init_safe_env_vars_applied')

源码引用: src/entrypoints/init.ts · 第 86–106 行(共 341 行)

  86|     // Make sure things get flushed on exit
  87|     setupGracefulShutdown()
  88|     profileCheckpoint('init_after_graceful_shutdown')
  89| 
  90|     // Initialize 1P event logging (no security concerns, but deferred to avoid
  91|     // loading OpenTelemetry sdk-logs at startup). growthbook.js is already in
  92|     // the module cache by this point (firstPartyEventLogger imports it), so the
  93|     // second dynamic import adds no load cost.
  94|     void Promise.all([
  95|       import('../services/analytics/firstPartyEventLogger.js'),
  96|       import('../services/analytics/growthbook.js'),
  97|     ]).then(([fp, gb]) => {
  98|       fp.initialize1PEventLogging()
  99|       // Rebuild the logger provider if tengu_1p_event_batch_config changes
 100|       // mid-session. Change detection (isEqual) is inside the handler so
 101|       // unchanged refreshes are no-ops.
 102|       gb.onGrowthBookRefresh(() => {
 103|         void fp.reinitialize1PEventLoggingIfConfigChanged()
 104|       })
 105|     })
 106|     profileCheckpoint('init_after_1p_event_logging')

源码引用: src/entrypoints/init.ts · 第 120–159 行(共 341 行)

 120|     // Initialize the loading promise early so that other systems (like plugin hooks)
 121|     // can await remote settings loading. The promise includes a timeout to prevent
 122|     // deadlocks if loadRemoteManagedSettings() is never called (e.g., Agent SDK tests).
 123|     if (isEligibleForRemoteManagedSettings()) {
 124|       initializeRemoteManagedSettingsLoadingPromise()
 125|     }
 126|     if (isPolicyLimitsEligible()) {
 127|       initializePolicyLimitsLoadingPromise()
 128|     }
 129|     profileCheckpoint('init_after_remote_settings_check')
 130| 
 131|     // Record the first start time
 132|     recordFirstStartTime()
 133| 
 134|     // Configure global mTLS settings
 135|     const mtlsStart = Date.now()
 136|     logForDebugging('[init] configureGlobalMTLS starting')
 137|     configureGlobalMTLS()
 138|     logForDiagnosticsNoPII('info', 'init_mtls_configured', {
 139|       duration_ms: Date.now() - mtlsStart,
 140|     })
 141|     logForDebugging('[init] configureGlobalMTLS complete')
 142| 
 143|     // Configure global HTTP agents (proxy and/or mTLS)
 144|     const proxyStart = Date.now()
 145|     logForDebugging('[init] configureGlobalAgents starting')
 146|     configureGlobalAgents()
 147|     logForDiagnosticsNoPII('info', 'init_proxy_configured', {
 148|       duration_ms: Date.now() - proxyStart,
 149|     })
 150|     logForDebugging('[init] configureGlobalAgents complete')
 151|     profileCheckpoint('init_network_configured')
 152| 
 153|     // Preconnect to the Anthropic API — overlap TCP+TLS handshake
 154|     // (~100-200ms) with the ~100ms of action-handler work before the API
 155|     // request. After CA certs + proxy agents are configured so the warmed
 156|     // connection uses the right transport. Fire-and-forget; skipped for
 157|     // proxy/mTLS/unix/cloud-provider where the SDK's dispatcher wouldn't
 158|     // reuse the global pool.
 159|     preconnectAnthropicApi()

init 与 cli 的衔接点

main.tsx 在交互路径 await init() 后 launchRepl;headless query 同样依赖 init。

daemon worker fast-path 不 enableConfigs——worker run() 内按需调用。

startupProfiler checkpoint 序列:cli_entry → cli_before_main_import → init_configs_enabled → cli_after_main_complete。

earlyInput 与 init 并行:用户按键在 main import 完成前已缓冲。

源码引用: src/entrypoints/init.ts · 第 1–50 行(共 341 行)

   1| import { profileCheckpoint } from '../utils/startupProfiler.js'
   2| import '../bootstrap/state.js'
   3| import '../utils/config.js'
   4| import type { Attributes, MetricOptions } from '@opentelemetry/api'
   5| import memoize from 'lodash-es/memoize.js'
   6| import { getIsNonInteractiveSession } from 'src/bootstrap/state.js'
   7| import type { AttributedCounter } from '../bootstrap/state.js'
   8| import { getSessionCounter, setMeter } from '../bootstrap/state.js'
   9| import { shutdownLspServerManager } from '../services/lsp/manager.js'
  10| import { populateOAuthAccountInfoIfNeeded } from '../services/oauth/client.js'
  11| import {
  12|   initializePolicyLimitsLoadingPromise,
  13|   isPolicyLimitsEligible,
  14| } from '../services/policyLimits/index.js'
  15| import {
  16|   initializeRemoteManagedSettingsLoadingPromise,
  17|   isEligibleForRemoteManagedSettings,
  18|   waitForRemoteManagedSettingsToLoad,
  19| } from '../services/remoteManagedSettings/index.js'
  20| import { preconnectAnthropicApi } from '../utils/apiPreconnect.js'
  21| import { applyExtraCACertsFromConfig } from '../utils/caCertsConfig.js'
  22| import { registerCleanup } from '../utils/cleanupRegistry.js'
  23| import { enableConfigs, recordFirstStartTime } from '../utils/config.js'
  24| import { logForDebugging } from '../utils/debug.js'
  25| import { detectCurrentRepository } from '../utils/detectRepository.js'
  26| import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
  27| import { initJetBrainsDetection } from '../utils/envDynamic.js'
  28| import { isEnvTruthy } from '../utils/envUtils.js'
  29| import { ConfigParseError, errorMessage } from '../utils/errors.js'
  30| // showInvalidConfigDialog is dynamically imported in the error path to avoid loading React at init
  31| import {
  32|   gracefulShutdownSync,
  33|   setupGracefulShutdown,
  34| } from '../utils/gracefulShutdown.js'
  35| import {
  36|   applyConfigEnvironmentVariables,
  37|   applySafeConfigEnvironmentVariables,
  38| } from '../utils/managedEnv.js'
  39| import { configureGlobalMTLS } from '../utils/mtls.js'
  40| import {
  41|   ensureScratchpadDir,
  42|   isScratchpadEnabled,
  43| } from '../utils/permissions/filesystem.js'
  44| // initializeTelemetry is loaded lazily via import() in setMeterState() to defer
  45| // ~400KB of OpenTelemetry + protobuf modules until telemetry is actually initialized.
  46| // gRPC exporters (~700KB via @grpc/grpc-js) are further lazy-loaded within instrumentation.ts.
  47| import { configureGlobalAgents } from '../utils/proxy.js'
  48| import { isBetaTracingEnabled } from '../utils/telemetry/betaSessionTracing.js'
  49| import { getTelemetryAttributes } from '../utils/telemetryAttributes.js'
  50| import { setShellIfWindows } from '../utils/windowsPaths.js'

源码引用: src/entrypoints/cli.tsx · 第 95–106 行(共 308 行)

  95|   ) {
  96|     profileCheckpoint('cli_computer_use_mcp_path')
  97|     const { runComputerUseMcpServer } = await import(
  98|       '../utils/computerUse/mcpServer.js'
  99|     )
 100|     await runComputerUseMcpServer()
 101|     return
 102|   }
 103| 
 104|   // Fast-path for `--daemon-worker=<kind>` (internal — supervisor spawns this).
 105|   // Must come before the daemon subcommand check: spawned per-worker, so
 106|   // perf-sensitive. No enableConfigs(), no analytics sinks at this layer —

daemon / templates / environment-runner 快路径

除 bridge/bg 外,cli 还有多条 headless fast-path:

  • claude daemon [sub] → daemonMain,enableConfigs + initSinks
  • claude new|list|reply → templatesMain,process.exit(0) 强制退出(Ink fleet TUI 残留 handle)
  • claude environment-runner / self-hosted-runner → 各自 main

共同模式:profileCheckpoint 标记分支 → dynamic import 专用 main → return,不加载 main.tsx。

--bare 在 fallthrough 前设置 SIMPLE,影响 Commander 构建时可见的选项集,与 init 内 enableConfigs 互补。

源码引用: src/entrypoints/cli.tsx · 第 164–180 行(共 308 行)

 164|     }
 165| 
 166|     await bridgeMain(args.slice(1))
 167|     return
 168|   }
 169| 
 170|   // Fast-path for `claude daemon [subcommand]`: long-running supervisor.
 171|   if (feature('DAEMON') && args[0] === 'daemon') {
 172|     profileCheckpoint('cli_daemon_path')
 173|     const { enableConfigs } = await import('../utils/config.js')
 174|     enableConfigs()
 175|     const { initSinks } = await import('../utils/sinks.js')
 176|     initSinks()
 177|     const { daemonMain } = await import('../daemon/main.js')
 178|     await daemonMain(args.slice(1))
 179|     return
 180|   }

源码引用: src/entrypoints/cli.tsx · 第 211–222 行(共 308 行)

 211|       default:
 212|         await bg.handleBgFlag(args)
 213|     }
 214|     return
 215|   }
 216| 
 217|   // Fast-path for template job commands.
 218|   if (
 219|     feature('TEMPLATES') &&
 220|     (args[0] === 'new' || args[0] === 'list' || args[0] === 'reply')
 221|   ) {
 222|     profileCheckpoint('cli_templates_path')

init 错误路径与 scratchpad

init.ts 后半(未在子章节全文展开)还包含:

  • ConfigParseError → dynamic import showInvalidConfigDialog(避免 init 路径加载 React)
  • ensureScratchpadDir — isScratchpadEnabled 时创建 scratchpad
  • shutdownLspServerManager — gracefulShutdown 注册
  • waitForRemoteManagedSettingsToLoad — SDK 测试防 deadlock timeout

init 使用 memoize:测试与重复入口可安全多次 await init()。

setMeterState 延迟 initializeTelemetry,OpenTelemetry protobuf 仅在需要 meter 时加载。

源码引用: src/entrypoints/init.ts · 第 160–200 行(共 341 行)

 160| 
 161|     // CCR upstreamproxy: start the local CONNECT relay so agent subprocesses
 162|     // can reach org-configured upstreams with credential injection. Gated on
 163|     // CLAUDE_CODE_REMOTE + GrowthBook; fail-open on any error. Lazy import so
 164|     // non-CCR startups don't pay the module load. The getUpstreamProxyEnv
 165|     // function is registered with subprocessEnv.ts so subprocess spawning can
 166|     // inject proxy vars without a static import of the upstreamproxy module.
 167|     if (isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)) {
 168|       try {
 169|         const { initUpstreamProxy, getUpstreamProxyEnv } = await import(
 170|           '../upstreamproxy/upstreamproxy.js'
 171|         )
 172|         const { registerUpstreamProxyEnvFn } = await import(
 173|           '../utils/subprocessEnv.js'
 174|         )
 175|         registerUpstreamProxyEnvFn(getUpstreamProxyEnv)
 176|         await initUpstreamProxy()
 177|       } catch (err) {
 178|         logForDebugging(
 179|           `[init] upstreamproxy init failed: ${err instanceof Error ? err.message : String(err)}; continuing without proxy`,
 180|           { level: 'warn' },
 181|         )
 182|       }
 183|     }
 184| 
 185|     // Set up git-bash if relevant
 186|     setShellIfWindows()
 187| 
 188|     // Register LSP manager cleanup (initialization happens in main.tsx after --plugin-dir is processed)
 189|     registerCleanup(shutdownLspServerManager)
 190| 
 191|     // gh-32730: teams created by subagents (or main agent without
 192|     // explicit TeamDelete) were left on disk forever. Register cleanup
 193|     // for all teams created this session. Lazy import: swarm code is
 194|     // behind feature gate and most sessions never create teams.
 195|     registerCleanup(async () => {
 196|       const { cleanupSessionTeams } = await import(
 197|         '../utils/swarm/teamHelpers.js'
 198|       )
 199|       await cleanupSessionTeams()
 200|     })

本章小结与延伸

cli = argv 路由器 + 冷启动优化;init = 全局一次性初始化。全量交互 CLI 在 main.tsx。 继续学习:

  • sdk-types
  • screens repl-screen
Prev
模块: entrypoints
Next
sdk-types · core / control / runtime 类型体系