芥末
发布于 2026-04-02 / 0 阅读
0
0

Claude Code 源码泄露暴露的 12 个工程实现细节

2026 年 3 月 31 日,Claude Code 的 npm 发布包携带了 Source Map(源码映射文件)。Source Map 通常用于把打包压缩后的 JavaScript 映射回 TypeScript 源码,方便调试;一旦映射文件包含源码内容,或者指向可访问的远程源码位置,发布包就可能暴露大量内部实现。

这次暴露的内容指向 R2 对象存储上的未混淆 TypeScript 源码,规模大约是 1,884 个 TypeScript 文件、51 万行代码。源码快照能看到 Claude Code 作为一个 AI 编码工具,在本地终端、模型提示词、安全权限、提交归因、功能灰度和自主 Agent Runtime 上做了哪些工程设计。

需要先划清边界:泄露快照只能代表当时构建状态,不等于所有功能都对外可用。源码里不少逻辑被 process.env.USER_TYPE === 'ant'feature() 开关保护,外部发布版可能已经在编译阶段移除了相关分支。

flowchart LR
    A[npm 发布包] --> B[Source Map]
    B --> C[R2 存储桶源码路径]
    C --> D[TypeScript 源码快照]

    D --> E[终端产品功能]
    D --> F[Prompt 与模型补丁]
    D --> G[权限与安全系统]
    D --> H[Feature Flags]
    D --> I[内部实验逻辑]

    D --> J[构建流程]
    J --> K[外部发布版]
    J -.按 USER_TYPE / feature 裁剪.-> L[内部功能被移除或隐藏]

1. Buddy:一个完整的终端电子宠物系统

源码里有一个叫 Buddy 的电子宠物系统,相关文件包括:

  • src/buddy/companion.ts
  • src/buddy/types.ts
  • src/buddy/sprites.ts

它不是简单的彩蛋占位,而是已经实现了物种、稀有度、属性、装饰、动画和防作弊逻辑的一套完整系统。

模块设计
物种18 种,例如 Duck、Capybara、Axolotl、Cactus、Dragon
稀有度Common 60%、Uncommon 25%、Rare 10%、Epic 4%、Legendary 1%
属性DEBUGGING、PATIENCE、CHAOS、WISDOM、SNARK
外观帽子系统,例如 Crown、Top Hat、Propeller
特殊版本1% 概率生成闪光版
动画每只宠物有 3 帧 ASCII art,用于呼吸、尾巴摆动等效果
个性名字和性格在首次“孵化”时由模型生成

真正有工程味的是它的防作弊方式。宠物的核心属性并不完全存放在配置文件里,而是根据 userId + salt 计算哈希,再用确定性伪随机数生成。

flowchart TD
    A[userId + salt] --> B[Hash]
    B --> C[确定性 PRNG]
    C --> D[按权重抽取物种]
    C --> E[按权重抽取稀有度]
    C --> F[生成五维属性]
    C --> G[判断是否闪光版]

    H[本地配置文件] --> I[只保存名字和性格]
    D --> J[最终 Buddy]
    E --> J
    F --> J
    G --> J
    I --> J

这种设计带来一个直接结果:用户就算手动改配置文件,也只能改名字、性格之类的“灵魂”信息,不能把普通鸭子强行改成传奇龙。因为每次启动时,系统都会重新根据种子计算物种和稀有度。

源码注释里还能看到一个时间窗口:2026 年 4 月 1 日到 4 月 7 日。friend-2026-401 这个 salt 也暗示它和 4 月 1 日有关。时间判断使用本地时区,而不是统一用 UTC,这样不同地区的用户会在当地时间触发,形成更分散的上线节奏。

2. 公开仓库贡献时会自动进入“隐藏身份”模式

相关文件:

  • src/utils/undercover.ts
  • src/utils/commitAttribution.ts

Claude Code 有一套专门面向公开仓库的提交保护逻辑。它的目标是避免 Anthropic 员工在给开源项目提交代码时,把内部模型代号、AI 工具痕迹或归因信息带进 commit message 和 Pull Request(PR,代码合并请求)描述里。

这类模式会阻止以下内容进入公开提交信息:

禁止内容风险
内部模型代号暴露内部命名体系
“Claude Code” 字样暴露使用的内部工具链
AI 身份描述让提交看起来不像普通开发者提交
Co-Authored-By泄露自动生成或协作归因
“1-shotted”“3-shotted”暴露内部效率指标

逻辑上,它默认把非内部仓库当成公开仓库处理。只有命中内部仓库白名单时,才关闭这个模式。源码注释里的原则也很明确:如果不能确定仓库是否内部,就继续保持隐藏身份。

这个设计本身解决了一个真实问题:AI 编码工具如果自动生成提交说明,很容易把内部上下文、模型名称、工具名称带出去。但它也引入了另一个问题:内部仓库白名单本身可能泄露仓库名。也就是说,白名单不仅是控制逻辑,也是敏感信息。

3. Claude Code 判断用户在骂它,靠的是正则表达式

相关文件:

  • src/utils/userPromptKeywords.ts

“AI 公司怎么检测用户在骂脏话?”答案很朴素:正则表达式。

源码中有一组负面情绪关键词匹配规则,用于识别用户输入里是否包含 wtfshitfuck youthis sucksso frustrating 之类的表达。简化后大概是这种结构:

const negativePattern =
  /\b(wtf|shit|fuck you|this sucks|so frustrating|damn it)\b/i

const keepGoingPattern =
  /\b(continue|keep going|go on)\b/i

这类检测不需要调用大语言模型(LLM,Large Language Model)。原因很简单:每条用户消息都调用一次模型做情绪分类,延迟和成本都会增加;而检测明显脏话、抱怨语句、继续执行意图,用正则已经足够。

方案延迟成本适合场景
正则匹配极低几乎为 0脏话、继续执行、固定关键词
小模型分类低到中有成本语义稍复杂的意图识别
大模型分类中到高成本高需要上下文推理的复杂判断

同一个文件里还有用于识别 “continue”“keep going”“go on” 的规则。这个信号可以帮助系统判断用户是在要求 Claude 继续完成任务,而不是开始一段普通闲聊。

4. 内部提交会记录“几次提示完成任务”

相关文件:

  • src/utils/attribution.ts
  • src/utils/commitAttribution.ts

内部提交信息里会带有类似这样的生成归因:

Generated with Claude Code (93% 3-shotted by claude-opus-4-5, 2 memories recalled)

这里的 3-shotted 表示完成任务用了 3 次用户提示;1-shotted 表示一次提示就完成。在公开仓库的隐藏身份模式下,这类信息会被去掉。

更重要的是,源码里有一套逐字符归因追踪系统,用来计算一个文件里有多少字符来自 AI 修改,多少来自人类修改。它的核心思路是对修改前后的文件做前缀、后缀匹配,定位真正变化的区间,再把变化归到对应来源。

flowchart LR
    A[修改前文件] --> C[前缀匹配]
    B[修改后文件] --> C
    C --> D[后缀匹配]
    D --> E[定位变化区间]
    E --> F[统计 AI / 人类字符数]
    F --> G[生成 commit trailer / PR 描述]

一个简化例子:

修改前: abcDEFxyz
修改后: abc123xyz

共同前缀: abc
共同后缀: xyz
变化区域: DEF -> 123

这种算法并不需要理解代码语义,只要在文件级别追踪变化来源,就能得到足够可用的统计指标。

源码注释还记录过一次非常夸张的归因统计 bug:837 个快照、280 个文件被错误地跨快照累加,而不是取最新值,最终追踪字符数达到约 1.15 × 10^15。一个 5KB 文件可能被统计成贡献了千万亿级别字符。这个案例说明,归因系统看起来只是统计功能,但只要和自动化流程、数据看板、内部指标绑定,错误累加就会被放大得非常离谱。

5. 宠物物种名用十六进制编码,避免触发构建扫描器

相关文件:

  • src/buddy/types.ts

Buddy 系统里有个有趣的工程绕法:物种名没有全部直接写成字符串字面量,而是用 String.fromCharCode 在运行时拼出来。

原因是 Capybara 同时也是内部模型代号。构建系统里有扫描器会检查构建产物是否包含内部代号;如果把 capybara 直接打进 bundle,可能误触发扫描规则。

简化写法如下:

const c = String.fromCharCode

export const duck =
  c(0x64, 0x75, 0x63, 0x6b) as 'duck'

export const capybara =
  c(0x63, 0x61, 0x70, 0x79, 0x62, 0x61, 0x72, 0x61) as 'capybara'

关键点在于:扫描器检查的是构建输出,而不是 TypeScript 源码。运行时构造字符串可以让敏感字面量不直接出现在打包产物里。

虽然真正冲突的是 Capybara,但源码里把所有物种都做了同样处理。这样做可以保持风格一致,也避免未来某个物种名再次和内部代号冲突。

6. 源码注释里记录了大量线上事故

相关文件分布在:

  • src/main.tsx
  • src/services/PromptSuggestion/promptSuggestion.ts
  • src/services/compact/autoCompact.ts
  • src/utils/toolResultStorage.ts
  • src/utils/messages.ts
  • src/components/Messages.tsx

这些注释最有价值的地方,不是解释某一行代码做什么,而是记录了真实线上事故:触发条件、影响范围、成本变化、事件编号和修复背景。

Prompt Cache 的两个事故

Anthropic API 的 prompt caching 依赖请求前缀匹配。只要前缀变化,缓存就可能失效。对于 Claude Code 这种每次都带大量上下文、工具描述和系统提示的工具来说,缓存命中率直接影响 token 成本。

一个简化的缓存 key 可以理解为:

cacheKey = hash(
  promptPrefix +
  toolDescriptions +
  model +
  effort +
  otherCacheableParams
)
事故触发原因结果
随机 UUID 导致缓存失效临时文件路径里的 UUID 出现在工具描述中,每次 SDK 调用都会变化输入 token 成本增加 12 倍
effort: 'low' 导致缓存写入暴涨effort 是 cache key 的一部分,fork 请求加了该参数缓存命中率从 92.7% 掉到 61%,写入量增加 45 倍

这里的经验很直接:任何出现在缓存前缀里的随机值、时间戳、临时路径、动态参数,都可能让 prompt cache 形同虚设。

Auto-compact 无限重试

自动压缩上下文的服务曾出现连续失败重试问题。注释里记录的数据是:1,279 个会话发生了 50 次以上连续失败,单个会话最高 3,272 次,每天浪费约 25 万次 API(应用程序编程接口)调用。

flowchart TD
    A[上下文接近上限] --> B[触发 auto-compact]
    B --> C{压缩成功?}
    C -- 是 --> D[继续会话]
    C -- 否 --> E[重试]
    E --> B
    E -.缺少熔断.-> F[连续失败数千次]

这种事故通常不是算法问题,而是缺少熔断和退避策略。失败后如果继续立即重试,系统会把一次功能失败放大成全局成本问题。

模型行为和 Prompt 边界问题

还有几类事故来自模型对 prompt 边界的误解:

现象原因影响
tool_result 让模型沉默某些模型把空工具结果误判为对话结束边界输出停止,返回空内容
tool_reference 展开触发停止序列服务端展开成 <functions> 标签,和系统 prompt 工具块同名A/B 测试中约 10% 概率误停
Prompt 尾部出现特殊结构模型把结构标签误解为角色切换或停止信号任务中断或无输出

LLM 应用里,“内容为空”不一定是无害状态。空字符串、空工具结果、尾部 XML 标签、和系统提示同名的标签,都可能改变模型对上下文边界的判断。

前端渲染、JSON 缓存和同步调用

源码注释还记录了几类资源问题:

问题具体表现工程教训
非虚拟化消息渲染约 2,000 条消息导致 59GB RSS(常驻内存集),每秒 14K 次 mmap/munmap长列表必须虚拟化
JSON 解析 memoizelodash memoize 缓存每个唯一 JSON 字符串且不释放对无限输入做 memoize 等于制造内存泄漏
spawnSync 同步调用一个同步调用占整个进程 7.2% CPU 时间CLI 工具也要避免高频同步阻塞

这些事故共同说明:AI 编码工具不是只有模型调用成本,终端渲染、文件解析、进程调用、缓存策略都会变成生产问题。

7. 源码暴露了一套内部代号体系

相关文件:

  • src/constants/prompts.ts
  • src/utils/fastMode.ts
  • src/utils/betas.ts

源码里能看到不少内部代号,它们散落在模型选择、功能开关、实验名称和 API 路径中。

代号含义
Capybara模型代号,存在 v4、v8 等版本
TenguClaude Code 项目代号,GrowthBook 实验常以 tengu_ 开头
Penguin Mode快速模式内部名称,硬编码使用 Opus 4.6,并有独立 API 端点、额度和冷却机制
Strudel出现在模型 fallback 逻辑中

内部代号本身不是问题,问题在于客户端代码、构建产物、feature flag 和 prompt 模板都可能成为代号泄露面。对于需要保密模型路线、实验计划或产品方向的团队,代号不应该随意进入外部可下载包。

8. 不同模型版本需要不同 Prompt 补丁

相关文件:

  • src/constants/prompts.ts

Claude Code 并不是只写一份通用系统提示词,然后让所有模型版本共用。源码里有针对不同模型版本的 Prompt(提示词)补丁,并用类似 @[MODEL LAUNCH] 的标签标记。

以 Capybara v8 为例,源码注释里记录它的虚假声明率约为 29% 到 30%,而 v4 约为 16.7%。于是系统提示词里增加了几类行为约束。

模型问题Prompt 补丁方向
过度写注释默认不写注释,只有原因不明显时才解释 WHY
声称测试通过但实际失败失败时必须如实说明,不能缩小检查范围制造“绿色结果”
为了避免虚假声明而过度免责声明确认通过时直接说通过,不要无意义对冲
完成前不验证报告完成前运行测试、脚本或检查输出
只机械执行用户要求发现用户误解或相邻 bug 时直接指出

这其实是一种 Prompt 层面的猴子补丁:模型版本暴露出某类行为偏差后,应用层用系统提示词约束它。理想状态下,下一代模型会把这些行为内化掉,应用层补丁可以删除;现实中,产品往往需要在模型迭代和上线节奏之间做过渡。

这些补丁当时只对内部 ant 用户生效,并通过 A/B 测试验证。这个细节也说明,AI 产品的行为调整不只是“改一句提示词”,而是带实验、指标和分批放量的工程流程。

9. 自动权限系统里有一个 yoloClassifier

相关文件:

  • src/utils/permissions/yoloClassifier.ts
  • src/tools/BashTool/bashPermissions.ts

Claude Code 的自动模式必须判断工具调用是否安全。比如模型想执行 Bash 命令、读写文件、调用 MCP(Model Context Protocol,模型上下文协议)工具时,系统要决定是直接允许、要求用户确认,还是拒绝。

源码里这个分类器叫 yoloClassifier。名字很轻松,但它处理的是安全核心路径。

flowchart TD
    A[模型请求工具调用] --> B{工具类型}
    B --> C[Bash 命令]
    B --> D[文件操作]
    B --> E[MCP / OAuth 工具]

    C --> F[命令解析与危险模式匹配]
    D --> G[路径与权限检查]
    E --> H[认证与授权检查]

    F --> I[yoloClassifier + 权限模板]
    G --> I
    H --> I

    I --> J{决策}
    J -- allow --> K[直接执行]
    J -- ask --> L[请求用户确认]
    J -- deny --> M[拒绝执行]

源码里区分了外部用户和 Anthropic 内部员工两套权限模板。安全相关代码量也很大:仅 Bash 权限检查就有 2,600 行以上,OAuth 和 MCP 认证也有 2,600 行左右。

注释中还引用了具体安全事件,例如通配符漏洞和 RCE(Remote Code Execution,远程代码执行)修复。环境变量保护列表也不是拍脑袋写的,而是根据真实 30 天权限请求数据生成。这个设计方向很务实:安全规则需要来自真实使用数据,而不是只靠静态威胁想象。

10. Feature Flags 暴露产品路线,KAIROS 指向自主 Agent Runtime

相关位置:

  • 搜索 feature( 可以看到 150 多处调用
  • src/bootstrap/state.ts
  • src/constants/prompts.ts

Claude Code 大量使用编译时 Feature Flags(功能开关)。许多已公开能力都能在这些 flag 里看到痕迹,例如语音模式、后台会话、浏览器自动化、多 Agent 协调等。也有一些名字在公开文档里找不到,例如 ULTRAPLANANTI_DISTILLATION_CCLODESTONECCR_MIRROR

其中最能体现产品方向的是 KAIROS。它不是普通定时循环,而是一个更完整的自主 Agent Runtime。

公开版 /loop 可以理解成“定时重复执行同一个 prompt”。KAIROS 更像驻留在终端里的长期运行进程:有心跳、有休眠工具、能感知用户是否在看终端,还能初始化 agent team,并支持只读 viewer mode 接入正在运行的 session。

能力/loopKAIROS
运行方式定时重复 prompt持续驻留的自主 runtime
系统提示偏响应式“自主寻找有用工作”
心跳机制使用 <tick> 保持活跃
空闲处理等下一轮必须调用 SleepTool
终端焦点感知不明显能感知用户是否在看
Agent team不强调启动时初始化团队
旁路观察不强调支持只读 viewer mode attach

KAIROS 的提示词方向大致是:

You are an autonomous agent.
Use the available tools to do useful work.
When idle, call SleepTool instead of wasting tokens.

这代表一种不同于“用户问一句、模型答一句”的交互模型。它把 AI 编码工具从命令式助手推进到半自主协作者:用户不需要每一步都下命令,Agent 可以根据项目状态寻找下一件有价值的事。

11. Bun 的 DCE 可能静默删掉代码路径

相关文件:

  • src/tools/BashTool/bashPermissions.ts

源码注释记录了一个 Bun 编译器相关问题:当某个函数复杂度过高时,Bun 的 feature() 求值器可能超过内部复杂度预算。复杂度被 import alias 等因素推高后,Bun 可能静默把某些三元表达式折叠为 false,导致关键代码路径被删掉,而且没有报错、没有警告。

DCE(Dead Code Elimination,死代码消除)本来是构建优化:如果编译器确认某段代码永远不会执行,就把它从产物里删掉。问题在于,错误的常量折叠会把本来应该存在的检查删掉。

简化后可以理解成:

// 期望:flag 开启时构造权限检查
const pendingCheck = feature('some_permission_check')
  ? buildPendingClassifierCheck()
  : undefined

错误构建后可能变成:

// 错误:编译器静默折叠为 false,检查路径消失
const pendingCheck = undefined

源码里的规避方式是把部分 import alias 改成顶层 const 重绑定,降低函数复杂度,让 Bun 的求值器不要越过那个隐藏阈值。

// 用顶层绑定降低复杂度
const buildCheck = importedBuildPendingClassifierCheck
const enableCheck = feature('some_permission_check')

这个问题对安全代码尤其危险。权限检查、命令分类、危险操作拦截这类路径,不应该只依赖“构建器应该会正确处理”。更稳的做法是加上构建产物检查、关键路径测试和运行时断言。

12. print.ts 是一个 5,500 行以上的巨型文件

相关文件:

  • src/cli/print.ts

src/cli/print.ts 超过 5,500 行,里面集成了大量职责:

职责内容
消息队列处理输入输出流转
工具装配组合 Bash、文件、MCP 等工具
MCP 编排管理外部工具协议调用
会话状态维护上下文和运行状态
权限系统处理确认、拒绝和自动执行
分析埋点记录行为和指标
文件持久化保存会话和中间结果
优雅关闭处理中断和资源清理

这类文件通常被称为 God File。它不一定马上导致 bug,但会显著增加修改成本:任何人想改一个小功能,都要理解消息流、权限、状态、工具调用和退出逻辑之间的耦合。

对于 LLM 辅助开发,这个文件也提供了一个很现实的提醒:模型很擅长在已有上下文里继续添加逻辑,但如果缺少架构边界,它会倾向于把新逻辑堆进“当前最方便改的文件”。时间一长,文件会越来越大,职责会越来越混。

更稳的工程约束包括:

约束作用
文件长度预算防止单文件无限增长
按职责拆模块把队列、权限、工具、状态、持久化分开
集成测试覆盖主流程拆分时避免破坏 CLI 行为
架构评审防止“临时加一点”持续累积
明确模块所有权让改动落在正确位置

从这些细节里能看到的工程规律

把这些实现放在一起看,Claude Code 不是一个简单的“终端里包一层模型 API”的工具。它有本地产品体验、内部实验系统、安全权限体系、模型行为补丁、提交归因系统和长期自主 Agent 的雏形。

几个规律尤其清晰:

  1. AI 产品的成本问题经常来自上下文细节。
    一个随机 UUID、一个额外参数、一个工具描述变化,都可能让 prompt cache 失效,直接放大 token 成本。

  2. 模型行为需要工程化治理。
    虚假声明、过度注释、不验证结果、误判停止序列,都不能只靠“换更好的模型”解决。Prompt 补丁、A/B 测试、事故注释和回归测试都要跟上。

  3. 自动执行能力越强,权限系统越重。
    当模型能执行 Bash、读写文件、调用 MCP 工具时,安全分类器、环境变量保护、确认策略和历史漏洞修复都会成为核心代码,而不是外围功能。

  4. Feature Flags 既能控制发布,也会泄露方向。
    编译时开关可以隐藏未发布功能,但 flag 名称、实验前缀、内部代号和 prompt 模板仍然可能暴露产品路线。

  5. LLM 辅助开发更需要架构边界。
    工具越会写代码,越容易快速堆功能。没有模块边界、文件长度限制和职责拆分,复杂度会集中到少数巨型文件里。

Claude Code 的源码快照展示了一个 AI 编码工具真实工程化的一面:有好玩的终端宠物,也有缓存事故、权限漏洞、编译器坑和 prompt 补丁。真正难的不是把模型接进终端,而是在模型能持续写代码、跑命令、改文件之后,仍然让系统可控、可观测、可维护。


评论