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.tssrc/buddy/types.tssrc/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.tssrc/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 公司怎么检测用户在骂脏话?”答案很朴素:正则表达式。
源码中有一组负面情绪关键词匹配规则,用于识别用户输入里是否包含 wtf、shit、fuck you、this sucks、so 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.tssrc/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.tsxsrc/services/PromptSuggestion/promptSuggestion.tssrc/services/compact/autoCompact.tssrc/utils/toolResultStorage.tssrc/utils/messages.tssrc/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 解析 memoize | lodash memoize 缓存每个唯一 JSON 字符串且不释放 | 对无限输入做 memoize 等于制造内存泄漏 |
spawnSync 同步调用 | 一个同步调用占整个进程 7.2% CPU 时间 | CLI 工具也要避免高频同步阻塞 |
这些事故共同说明:AI 编码工具不是只有模型调用成本,终端渲染、文件解析、进程调用、缓存策略都会变成生产问题。
7. 源码暴露了一套内部代号体系
相关文件:
src/constants/prompts.tssrc/utils/fastMode.tssrc/utils/betas.ts
源码里能看到不少内部代号,它们散落在模型选择、功能开关、实验名称和 API 路径中。
| 代号 | 含义 |
|---|---|
| Capybara | 模型代号,存在 v4、v8 等版本 |
| Tengu | Claude 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.tssrc/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.tssrc/constants/prompts.ts
Claude Code 大量使用编译时 Feature Flags(功能开关)。许多已公开能力都能在这些 flag 里看到痕迹,例如语音模式、后台会话、浏览器自动化、多 Agent 协调等。也有一些名字在公开文档里找不到,例如 ULTRAPLAN、ANTI_DISTILLATION_CC、LODESTONE、CCR_MIRROR。
其中最能体现产品方向的是 KAIROS。它不是普通定时循环,而是一个更完整的自主 Agent Runtime。
公开版 /loop 可以理解成“定时重复执行同一个 prompt”。KAIROS 更像驻留在终端里的长期运行进程:有心跳、有休眠工具、能感知用户是否在看终端,还能初始化 agent team,并支持只读 viewer mode 接入正在运行的 session。
| 能力 | /loop | KAIROS |
|---|---|---|
| 运行方式 | 定时重复 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 的雏形。
几个规律尤其清晰:
-
AI 产品的成本问题经常来自上下文细节。
一个随机 UUID、一个额外参数、一个工具描述变化,都可能让 prompt cache 失效,直接放大 token 成本。 -
模型行为需要工程化治理。
虚假声明、过度注释、不验证结果、误判停止序列,都不能只靠“换更好的模型”解决。Prompt 补丁、A/B 测试、事故注释和回归测试都要跟上。 -
自动执行能力越强,权限系统越重。
当模型能执行 Bash、读写文件、调用 MCP 工具时,安全分类器、环境变量保护、确认策略和历史漏洞修复都会成为核心代码,而不是外围功能。 -
Feature Flags 既能控制发布,也会泄露方向。
编译时开关可以隐藏未发布功能,但 flag 名称、实验前缀、内部代号和 prompt 模板仍然可能暴露产品路线。 -
LLM 辅助开发更需要架构边界。
工具越会写代码,越容易快速堆功能。没有模块边界、文件长度限制和职责拆分,复杂度会集中到少数巨型文件里。
Claude Code 的源码快照展示了一个 AI 编码工具真实工程化的一面:有好玩的终端宠物,也有缓存事故、权限漏洞、编译器坑和 prompt 补丁。真正难的不是把模型接进终端,而是在模型能持续写代码、跑命令、改文件之后,仍然让系统可控、可观测、可维护。