乐于分享
好东西不私藏

Claude Code 源码泄露始末与架构深度拆解

本文最后更新于2026-03-31,某些文章具有时效性,若有错误或已失效,请在下方留言或联系老夜

Claude Code 源码泄露始末与架构深度拆解


一段被遗忘的source map,让社区第一次看清了Claude Code的内部结构。这篇文章从事件本身讲起,再逐层拆解它的运行机制——从agent loop到工具系统,从任务编排到子代理协作,从上下文管理到权限治理。


Source Map事件:到底发生了什么?

这次事件的核心很简单:Claude Code早期npm发布包里把内联source map 一起带出去了。

安装包里的 cli.mjs 底部直接包含 sourceMappingURL=data:application/json;base64,...,而这段map里又带着足够多的原始源码信息,社区能够把接近原始结构的代码重新提取出来。

你可以把它理解成——不是服务器被黑,不是代码仓库被拖库,而是前端/Node打包产物里不小心把「反编译说明书」也一并发给了所有用户。

Dave Schumaker的记录显示,他是在本地 npm install -g @anthropic-ai/claude-code 后,直接从全局 node_modules 的23MB cli.mjs 里发现了这条超长的inline source map。


时间线还原

按现有公开材料,时间线大致是这样的:

第一步,Anthropic发布Claude Code研究预览版,用户通过npm安装。Dave的文章写明当时安装命令就是 npm install -g @anthropic-ai/claude-code

第二步,开发者打开安装后的 cli.mjs,先看到它是压缩过的单文件,再在文件底部发现 sourceMappingURL=data:application/json;base64,...,说明source map被直接内联进产物中。

第三步,Dave估算那一整行map内容有 18,360,183个字符,并据此判断「可能包含完整inline sources,可从头重建原始源码」。

第四步,Anthropic很快推了更新,把这段source map去掉;Dave重开文件后发现map已消失,并注意到旧包也被从npm侧清理了。

第五步,但因为他本地编辑器仍保留了旧文件状态,他最终通过Sublime Text的撤销操作把那段map重新找了出来,于是社区后续得以继续提取和分析源码。

第六步,GitHub上出现了 leeyeel/claude-code-sourcemap 这类仓库,README直接标注「Claude Code (Research Preview) – 0.2.8 with extracted source maps」,仓库里也能看到 srcvendorcli.mjsyoga.wasm 等文件。

这里最关键的细节有两个。第一,它不是单独的 .map 文件,而是inline source map,直接嵌在 cli.mjs 末尾,所以只要拿到安装包,就等于拿到映射信息。第二,被广泛传播和还原的版本是 0.2.8


这件事的本质

本质上,这是一次打包/发布链路失误

Source map本来是给调试用的,帮助把压缩后的bundle映射回原始源文件;如果map里还嵌了 sourcesContent 一类原始内容,就会极大降低逆向门槛,基本等于把「接近源码」的材料随包发出去了。

所以中文里把它叫「源码泄露」不算完全错,但更准确的说法是:npm发布包误带可还原源码的source map,导致社区重建出接近原始的Claude Code 0.2.8源码树。

这和内部Git仓库被盗、CI凭证泄漏、服务端入侵拿走monorepo,不是同一类事故。

另一个容易混淆的点是:后来网上还流传Claude Code的system prompt、行为策略和工具说明,那些属于「提示词/运行规范外泄」;而source map事件则是代码分发物带出了实现细节。这两类材料经常同时被讨论,才让外界感觉像是「一次性全泄了」。


Anthropic当时做了什么

从Dave的记录看,Anthropic的第一反应是迅速撤掉map并清理旧版本痕迹

他写到重启后发现 sourceMappingURL 消失,随后去npm追旧包时,又发现旧内容也在被移除,连npm cache中对相关tarball的引用都成了「想提取但已被purge」的状态。

不过,补救虽然快,但已经来不及完全阻断扩散。因为npm包是面向所有安装者分发的,只要有人在修复前装过、缓存过、或者保留过bundle文件,就可能离线提取map内容;后来GitHub上的还原仓库,也说明这份材料确实已经进入社区流通。


Claude Code架构拆解:不只是一个聊天壳

基于已还原的0.2.8代码仓库,以及shareAI-lab对Claude Code的抽象,Claude Code更像一个「模型驱动的agent harness」,而不是一个规则树很重的工作流系统。

最简抽象可以写成这一句:一个agent loop,外加工具系统、规划、知识加载、上下文压缩、子代理、任务系统、团队协作、以及隔离执行环境。


总体结构:六层模块

可以把Claude Code理解成6个模块:交互层、会话与提示层、agent loop、工具调度层、任务/子代理层、上下文与扩展层。

┌──────────────────────────────┐ 1Terminal UI / CLI          React + Ink, 输入/输出/确认   └──────────────┬───────────────┘               ┌──────────────▼───────────────┐ 2Session & Prompt Layer     system prompt / messages /    memory of current task       └──────────────┬───────────────┘               ┌──────────────▼───────────────┐ 3Agent Loop                 模型决定: 回答 / 调工具 / 继续 └───────┬─────────────┬────────┘                     ┌───────▼───────┐ ┌───▼─────────────────┐ 4Tool Bus     5Task Orchestrator read/write/     todo / subagent /    bash/git/...    background tasks    └───────┬───────┘ └───┬─────────────────┘                     ┌───────▼─────────────▼────────┐ 6Context & Extension Layer  skills / MCP / compact /      permission / isolation       └──────────────────────────────┘

这个结构里最核心的是第3层。Claude官方文档直接写到,Agent SDK运行的是「与Claude Code相同的execution loop」。


第一层:交互层

Dave的文章给了一个很直观的实现线索:Claude Code的CLI界面是用 React和Ink 写的,也就是把React组件思维搬进终端UI。这说明它不是传统readline式CLI,而是带状态、可重绘、可组件化的终端应用。

这层负责做几件事:

接收用户自然语言指令。

展示agent当前状态——等待、思考、执行中动画;Dave还看到了等待时随机显示的动词数组,以及ASCII asterisk动画字符组。

把模型输出、工具结果、确认提示和git/PR工作流反馈,组织成终端里的交互体验。


第二层:会话与提示层

启动时,CLI先建立终端UI,然后装配会话状态、系统规则、工具列表和当前任务上下文,再把这些统一送进模型循环。

从用户视角,你只看到「输入一句需求」;从系统视角,实际发生的是:

读取当前工作目录和仓库状态,作为任务环境的一部分。

初始化工具注册表,让模型知道「能调用什么」。

把system prompt、用户请求、已有消息历史拼成一次agent turn的输入。

所以Claude Code的启动不是「发个prompt」那么简单,而是把一个可执行工作台先搭起来,再让模型接管决策


第三层:Agent Loop——真正的核心

Claude Code的核心不是UI,而是agent loop。shareAI-lab给出的最小模型很清楚:把 messages[]systemtools 发给模型;如果模型返回 tool_use,代码就执行相应handler,把结果作为 tool_result 再塞回消息流;直到模型不再请求工具,循环结束。

这个设计的关键是「模型决定下一步,代码只负责执行」。 所以Claude Code的聪明,不主要来自写死的if-else流程,而来自模型在上下文中自己选择何时读文件、何时跑命令、何时继续提问、何时结束。

你可以用一个比喻来理解:

模型是驾驶员。harness是车。loop是发动机循环。工具就是方向盘、油门、刹车、后视镜。

消息流本质上是一个闭环,而不是线性问答:

用户请求  ↓系统提示 + 历史消息 + 工具定义  ↓Claude 生成下一步  ├─ 直接回答 → 输出给用户  └─ 请求调用工具          ↓     执行工具          ↓   工具结果写回消息流          ↓      Claude 再判断

这句话其实非常关键,因为它决定了Claude Code的边界:模型负责策略,代码负责执行和约束,产品体验来自二者的配合。


第四层:工具调度——最像「操作系统」的地方

Claude Code的工具层是它最像「操作系统」的地方。公开材料表明,它至少覆盖文件读写、shell、搜索、git,以及通过MCP接入外部工具的能力。

工具系统至少分成两类:

内建工具:例如read、write、edit、bash、grep一类高频原子能力。

外接工具:通过MCP暴露给模型的新能力,按JSON/协议描述接入。

工程上你可以把它理解成一个Tool Bus:

模型输出 tool_use   ↓Tool Dispatcher 解析工具名和参数   ↓匹配 handler   ├─ 文件系统工具   ├─ Shell 工具   ├─ Git 工具   ├─ 搜索/匹配工具   └─ MCP 外部工具   ↓执行结果标准化   ↓回写为 tool_result

这种设计的优势是原子、统一、可扩展。缺点也很明显:一旦权限控制或信任模型出问题,工具层就会成为攻击面,这也是后续安全漏洞被重点讨论的原因之一。

循环本身很小,真正决定产品边界的是你给模型提供了什么工具、工具描述得是否清楚、权限卡得是否合理。


第五层:任务系统与子代理

Claude Code之所以不像普通聊天机器人,是因为它不只「响应」,还会「推进任务」。

任务系统

任务系统通常至少包含三层:

Todo层:把当前目标显式拆成步骤,防止模型跳来跳去。

Task层:把较大任务拆成可独立执行的工作单元,带依赖关系、可持久化到磁盘,形成task graph。

Background/async层:把慢任务放后台执行,主会话继续前进。

没有显式planning时,agent会在任务间来回跳、忘步骤、失去焦点;加入Todo工具后,行为会稳定很多。这说明Claude Code的「靠谱感」不是凭空来的,而是靠外部化计划把模型从隐式思考拉回可见流程

子代理设计

当任务复杂到一个上下文装不下时,Claude Code就会进入subagent模式——把大任务拆成子问题,每个子问题在独立消息上下文里处理,避免主线程上下文被污染。

主 Agent ├─ 分析大目标 ├─ 拆成子任务 ├─ 为每个子任务启动独立上下文 ├─ 子 Agent 各自调查/实现/验证 └─ 汇总结果,决定下一步

再往上,就是agent teams:持久化队友或worker、异步mailbox沟通、共享协议做请求-响应协调、让agent自己扫描任务板并auto-claim任务。

这其实解释了为什么很多人觉得Claude Code很像一个小型多代理操作系统:主agent负责统筹,worker agent负责并行调查、实现、验证,再把结果汇总回来。


第六层:上下文管理与扩展

上下文管理:隐形核心

上下文管理是Claude Code这种产品真正的隐形核心。这一层通常包括三件事:

上下文选择:当前轮只喂最相关的文件、历史和任务状态。

上下文隔离:子代理各用各的消息空间,避免互相污染。

上下文压缩:长会话变成摘要、结论、待办,而不是无限堆历史。

这也是为什么Claude Code风格系统经常显得比普通长上下文聊天更稳。它不是简单依赖「大模型记得住」,而是主动做信息预算管理

按需加载

Claude Code不是把所有知识一次性塞进system prompt,而是按需加载。常驻的是最小系统规则和工具接口;具体领域知识、规范、风格约束、项目说明,在需要时以skill/doc/tool_result的形式注入。

这套机制非常像一个能长期工作的工程助理:它不是「什么都背在脑子里」,而是知道什么时候去拿文档、什么时候归纳旧信息、什么时候给自己腾上下文窗口。

扩展与权限

Claude Code不是封闭系统。MCP支持让它可以接入外部工具。但开放扩展的同时,权限治理也必须跟上,因为工具越多、可操作面越大,模型越像一个有执行能力的本地代理。

官方文档提到,Agent SDK提供对tools、permissions、cost limits和output的程序化控制,并且可以通过hooks在工具执行前拦截、修改或阻止调用。这一层负责的不是让系统更聪明,而是让它不至于失控。


并发与隔离:从「能做事」到「能并行做复杂事」

这里面最关键的是两点:后台任务和worktree隔离

后台任务意味着慢操作可以异步跑,agent不必被阻塞住;完成后再通过通知队列把结果回灌回来。

Worktree隔离则意味着不同任务在不同目录/工作树执行,减少并行改动互相踩踏的风险。

这正是Claude Code这类产品比「单次补全式AI」强很多的地方:它不是只会写一段代码,而是在尝试管理一个持续演进的软件工作面。


一个最小实现蓝图

如果你要自己做一个mini Claude Code,最小可行结构可以压缩成这样:

main() ├─ init_ui() ├─ load_tools() ├─ session = new_session() └─ while not done:      prompt = build_messages(session)      resp = model(prompt, tools)      if resp.has_tool_calls:         results = run_tools(resp.tool_calls)         session.append(results)      else:         render(resp)         done = true

然后按阶段往上加:

v1:工具注册和统一dispatcher。

v2:Todo规划。

v3:Subagent。

v4:Skills / MCP扩展。

v5:权限、hooks、成本限制、隔离执行。


我对Claude Code的结构判断

如果把所有公开材料压缩成一句话:

Claude Code = 终端中的React/Ink交互层 + 一个以tool_use为中心的agent loop + 原子工具集 + 计划/任务系统 + 子代理/团队执行 + 按需知识加载 + 上下文压缩 + 并发与worktree隔离。

这里最重要的认知是:它的「神秘感」并不主要来自某个惊人的hidden algorithm,而来自一整套非常克制的harness engineering。shareAI-lab反复强调「The model is the agent. The code is the harness」,这句话用来解释Claude Code很合适。

Claude Code的强,不在于它把流程写死,而在于它给模型提供了足够好的工作环境

看得见代码库。

动得了文件和shell。

记得住计划和任务。

能拆分并行工作。

不至于被长上下文压死。

真正复杂的地方,不是loop本身,而是loop周围的治理能力:工具如何描述、权限如何卡、任务怎么拆、上下文怎么收。


本文首发于微信公众号 Daily Buffer转载请注明出处