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」,仓库里也能看到 src、vendor、cli.mjs、yoga.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、工具调度层、任务/子代理层、上下文与扩展层。
┌──────────────────────────────┐│ 1. Terminal UI / CLI ││ React + Ink, 输入/输出/确认 │└──────────────┬───────────────┘│┌──────────────▼───────────────┐│ 2. Session & Prompt Layer ││ system prompt / messages / ││ memory of current task │└──────────────┬───────────────┘│┌──────────────▼───────────────┐│ 3. Agent Loop ││ 模型决定: 回答 / 调工具 / 继续 │└───────┬─────────────┬────────┘│ │┌───────▼───────┐ ┌───▼─────────────────┐│ 4. Tool Bus │ │ 5. Task Orchestrator││ read/write/ │ │ todo / subagent / ││ bash/git/... │ │ background tasks │└───────┬───────┘ └───┬─────────────────┘│ │┌───────▼─────────────▼────────┐│ 6. Context & 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[]、system、tools 发给模型;如果模型返回 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转载请注明出处
夜雨聆风