來源:市場資訊
(來源:51CTO技術棧)
![]()
編輯 | 云昭
// screens/REPL.tsx — lines 96-120 (trimmed for readability)// Dead code elimination: conditional importsconst useVoiceIntegration = feature('VOICE_MODE')? require('../hooks/useVoiceIntegration.js').useVoiceIntegration: () => ({ ... });const useFrustrationDetection = "external" === 'ant'? require('../components/FeedbackSurvey/useFrustrationDetection.js'): () => ({ state: 'closed', handleTranscriptSelect: () => {} });const useAntOrgWarningNotification = "external" === 'ant'? require('../hooks/notifs/useAntOrgWarningNotification.js'): () => {};// ... 200 more lines of importsABLATION_BASELINE, AGENT_MEMORY_SNAPSHOT, AGENT_TRIGGERS,AGENT_TRIGGERS_REMOTE, ALLOW_TEST_VERSIONS, ANTI_DISTILLATION_CC,AUTO_THEME, AWAY_SUMMARY, BASH_CLASSIFIER, BG_SESSIONS,BREAK_CACHE_COMMAND, BRIDGE_MODE, BUDDY, BUILDING_CLAUDE_APPS,BUILTIN_EXPLORE_PLAN_AGENTS, BYOC_ENVIRONMENT_RUNNER,CACHED_MICROCOMPACT, CCR_AUTO_CONNECT, CCR_MIRROR, CCR_REMOTE_SETUP,CHICAGO_MCP, COMMIT_ATTRIBUTION, COMPACTION_REMINDERS,CONNECTOR_TEXT, CONTEXT_COLLAPSE, COORDINATOR_MODE,COWORKER_TYPE_TELEMETRY, DAEMON, DIRECT_CONNECT,DOWNLOAD_USER_SETTINGS, DUMP_SYSTEM_PROMPT, ENHANCED_TELEMETRY_BETA,EXPERIMENTAL_SKILL_SEARCH, EXTRACT_MEMORIES, FILE_PERSISTENCE,FORK_SUBAGENT, HARD_FAIL, HISTORY_PICKER, HISTORY_SNIP,HOOK_PROMPTS, IS_LIBC_GLIBC, IS_LIBC_MUSL, KAIROS, KAIROS_BRIEF,KAIROS_CHANNELS, KAIROS_DREAM, KAIROS_GITHUB_WEBHOOKS,KAIROS_PUSH_NOTIFICATION, LODESTONE, MCP_RICH_OUTPUT, MCP_SKILLS,MEMORY_SHAPE_TELEMETRY, MESSAGE_ACTIONS, MONITOR_TOOL,NATIVE_CLIENT_ATTESTATION, NATIVE_CLIPBOARD_IMAGE, NEW_INIT,OVERFLOW_TEST_TOOL, PERFETTO_TRACING, POWERSHELL_AUTO_MODE,PROACTIVE, PROMPT_CACHE_BREAK_DETECTION, QUICK_SEARCH,REACTIVE_COMPACT, REVIEW_ARTIFACT, RUN_SKILL_GENERATOR,SELF_HOSTED_RUNNER, SHOT_STATS, SKILL_IMPROVEMENT,SLOW_OPERATION_LOGGING, SSH_REMOTE, STREAMLINED_OUTPUT, TEAMMEM,TEMPLATES, TERMINAL_PANEL, TOKEN_BUDGET, TORCH,TRANSCRIPT_CLASSIFIER, TREE_SITTER_BASH, TREE_SITTER_BASH_SHADOW,UDS_INBOX, ULTRAPLAN, ULTRATHINK, UNATTENDED_RETRY,UPLOAD_USER_SETTINGS, VERIFICATION_AGENT, VOICE_MODE,WEB_BROWSER_TOOL, WORKFLOW_SCRIPTSANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL,ANTHROPIC_BEDROCK_BASE_URL, ANTHROPIC_BETAS, ANTHROPIC_CUSTOM_HEADERS,ANTHROPIC_CUSTOM_MODEL_OPTION, ANTHROPIC_DEFAULT_HAIKU_MODEL,ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL,ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_FOUNDRY_BASE_URL,ANTHROPIC_MODEL, CLAUDE_CODE_COORDINATOR_MODE, ...// 458+ more// types/permissions.ts// Pure permission type definitions extracted to break import cycles.// to avoid circular dependencies.// schemas/hooks.ts// Hook Zod schemas extracted to break import cycles.// circular dependency between settings/types.ts and plugins/schemas.ts.// tasks.ts// Note: Returns array inline to avoid circular dependency issues// with top-level const// utils/systemPrompt.ts// Use inline env check instead of coordinatorModule to avoid circular// Lazy require to avoid circular dependency at module load time// utils/bash/ast.ts (line 2218)// circular import with bashPermissions.ts.logEvent('tengu_startup_telemetry', {entrypoint: entrypoint as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,action: 'hint_converted' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,variant: idleHintShownRef.current as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,// instead of this (what they do):logEvent('name', {key: value as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,// something like this:logSafeEvent('name', {key: SafeMetadata.from(value) // throws if value looks like a path/code// buddy/types.ts// One species name collides with a model-codename canary in// excluded-strings.txt. The check greps build output (not source),// so runtime-constructing the value keeps the literal out of the// bundle while the check stays armed for the actual codename.// All species encoded uniformly.const c = String.fromCharCodeexportconst duck = c(0x64,0x75,0x63,0x6b) as'duck'exportconst goose = c(0x67,0x6f,0x6f,0x73,0x65) as'goose'exportconst blob = c(0x62,0x6c,0x6f,0x62) as'blob'exportconst cat = c(0x63,0x61,0x74) as'cat'exportconst dragon = c(0x64,0x72,0x61,0x67,0x6f,0x6e) as'dragon'exportconst octopus = c(0x6f,0x63,0x74,0x6f,0x70,0x75,0x73) as'octopus'exportconst owl = c(0x6f,0x77,0x6c) as'owl'exportconst penguin = c(0x70,0x65,0x6e,0x67,0x75,0x69,0x6e) as'penguin'// ... 10 more species, all hex-encodedAnthropic 的工程師,居然在一個終端 AI 編程工具里,花時間做了一個“程序生成的寵物系統”,還帶稀有度分級(從 common 到 legendary)、命名物種、帽子、眼睛樣式、屬性分布……這件事本身還挺迷人的。
6. main.tsx:一個 4,683 行的入口文件
main.tsx 是 CLI 的入口文件,長度 4,683 行,里面包含:
所有 CLI 命令定義(claude、init、config、mcp、doctor 等)
使用 Commander.js 做的全部參數解析
完整的 OAuth 登錄流程
session 恢復邏輯
遠程 session 管理
啟動性能基準測試
插件加載
MDM(移動設備管理)配置
代碼注釋也解釋了為什么這么設計:
// main.tsx — lines 1-8// These side-effects must run before all other imports:// 1. profileCheckpoint marks entry before heavy module evaluation begins// 2. startMdmRawRead fires MDM subprocesses in parallel with the// remaining ~135ms of imports below// 3. startKeychainPrefetch fires both macOS keychain reads in parallel// (~65ms on every macOS startup)也就是說,這是一種“刻意為之”的架構:把所有東西放在一個文件里,減少 import 樹的深度。
因為 Bun 是 eager 執行 import 的。import 層級越深,啟動延遲越高。把邏輯集中在 main.tsx,就可以把導入層級控制在一層,而不是三四層。
為什么我覺得這是一個有問題的權衡:
他們為了節省大約 135ms 的啟動時間,讓入口文件變得幾乎不可讀。其實可以用懶加載的命令注冊機制達到類似效果——只有在用戶執行 claude init 時,才加載 init 模塊;只有在需要認證時,才加載 OAuth。
這也是大多數 CLI 工具的常規做法(比如 oclif、yargs 的 command modules,甚至 Commander 的子命令拆文件)。
反方觀點(而且是合理的)是:Bun 的模塊加載機制可能有一些 Node 沒有的“坑”,懶加載不一定穩定。而對于一個被頻繁調用的工具來說,135ms 可能確實很關鍵。
我個人對 Bun 的使用還不夠深入,不確定這是不是唯一能保證啟動速度的方案,但我懷疑是有替代路徑的。
7. 條件 require 模式
這是第 2 點和第 6 點的自然結果。在整個代碼庫里,尤其是 REPL.tsx 和 query.ts 中,你會反復看到類似這樣的代碼:
// query.ts — lines 15-22const reactiveCompact = feature('REACTIVE_COMPACT')? (require('./services/compact/reactiveCompact.js')as typeof import('./services/compact/reactiveCompact.js')): nullconst contextCollapse = feature('CONTEXT_COLLAPSE')? (require('./services/contextCollapse/index.js')as typeof import('./services/contextCollapse/index.js')): null本質上,這是在 ES module 里用 require(),再包一層編譯期的 feature 判斷,然后用 as typeof import(...) 把類型“補回來”。
之所以這么做,是因為:
- import
會被提升,并且在模塊加載時一定執行
- feature()
需要徹底阻止 import,才能讓死代碼消除生效
在模塊體內,只有 require() 可以做條件加載
但 require() 會丟失類型信息,所以要用類型斷言補回
為什么這很重要:
這里每一處都是類型系統的“漏洞”。as typeof import(...) 實際上是在告訴 TypeScript:“相信我,這個類型是對的。”一旦 reactiveCompact.js 的導出結構發生變化,這個斷言不會報編譯錯誤,只會在運行時炸。
有哪些替代方案:
import()(動態導入)可以返回 Promise,并且保留類型信息。雖然寫起來稍微麻煩一點(需要 await),但這是現代 JavaScript 里做條件模塊加載的標準方式。而 Bun 也是支持的。
這些問題背后的共同模式
如果把視角拉高一點來看,這些問題大多來自同一個根本原因:Claude Code 的演進速度,超過了它架構能夠承載的節奏。
你可以清楚地看到一層層“歷史沉積”。一個簡單的終端 REPL,逐漸長成了一個多 Agent 協調系統,疊加了語音模式、寵物系統、Vim 綁定、遠程會話等功能。新功能通過 feature flag 不斷加進去,但舊的 flag 卻沒有被及時清理。模塊之間的依賴關系擴展得越來越快,卻很少有人真正去重新劃定邊界。
這并不是 Anthropic 獨有的問題。幾乎所有高速發展的公司,代碼庫都會變成這樣。Claude Code 有意思的地方在于它的規模——這是當下最重要的 AI 產品之一,而它的源碼,暴露出的卻是和每一家創業公司一樣的工程現實:混亂、權衡、妥協。
代碼是能跑的,而且跑得很好。每天都有大量開發者依賴它。這一點,比“架構是否優雅”更重要。但這些代價,依然值得被正視。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.