Claude Code源码泄露之后,我把它丢给了Codex…

这两天,Claude Code 源码这件事在开发者圈传得很猛。
很多人的第一反应是吃瓜:这么大的团队,怎么会把源码弄出来;也有人第一时间去翻目录、找 prompt、看 Anthropic 到底藏了多少“祖传秘方”。
但我把这份代码交给 Codex 帮我一起通读之后,最大的感受反而不是八卦,而是另一件事:
我们第一次能比较近距离地看到,一个顶级 Coding Agent 到底是怎么被工程化落地的。
说白了,我原本以为自己会看到一个“模型 + 命令行壳子”的项目,结果看下来完全不是。Claude Code 更像一套跑在终端里的智能体运行时。它当然会调模型,但它真正的重心,不是“怎么生成一段回复”,而是“怎么让一个 Agent 在真实开发环境里持续工作、持续可控、持续不失控”。
这篇文章,我不想做八卦汇总,也不打算罗列功能清单。我想聊的是,我让 Codex 陪我一起读完这份代码以后,真正觉得有价值的几个观察。
一句话先下结论:
Claude Code 最厉害的地方,不是把模型塞进终端,而是把终端、权限、工具、上下文、任务系统和交互状态,揉成了一套完整运行时。
先说最直观的感觉:它根本不是“小工具”
我先扫了一遍工程目录,马上就能感觉到,这不是一个“命令行聊天程序”那么简单:
1assistant/
2bridge/
3cli/
4commands/
5components/
6coordinator/
7query/
8remote/
9server/
10services/
11skills/
12state/
13tasks/
14tools/
15utils/
16main.tsx
17QueryEngine.ts
18Tool.ts
19query.ts
这份目录本身就很说明问题。
如果一个项目核心只是“用户输入一句话,模型回一句话”,你不太会看到这么厚的 tools/、tasks/、state/、services/,也不太会把 QueryEngine.ts 和 query.ts 单独拎出来。更不会出现 assistant、coordinator、bridge、remote 这种一看就不是 Demo 项目会有的模块。
我读到这里时第一个判断就已经很明确了:
Claude Code 不是 CLI 套壳 LLM,而是 Agent Runtime。
这句话听起来像套话,但真看源码你会发现,它们的思路确实不一样。
有些 AI 产品的核心是 prompt,有些产品核心是模型调用,有些产品核心是 UI 包装;Claude Code 这套系统的核心,更像是“运行时调度”。也就是说,它不是在想“怎么回答你”,而是在想“你交代的事情,要怎么在一个复杂、真实、持续变化的工程环境里跑下去”。
第一眼就让我很服的一点:启动阶段就在抢时间
main.tsx 开头有一段代码,我一看就觉得这团队是真的在乎终端体验:
1profileCheckpoint('main_tsx_entry');
2startMdmRawRead();
3startKeychainPrefetch();
这段代码背后的意思很直接:主模块还没 import 完,就先把 MDM 配置读取、Keychain 预取这些慢操作并发启动,后面再找合适时机统一 await。
这种优化特别“命令行工程师”。
因为做 Web 的同学经常会把性能焦点放在首屏、接口、渲染;但做 CLI,用户对“快不快”的感知往往更直接。你敲下一条命令,如果 100 多毫秒都没有什么反馈,体感就已经开始变差了。更何况 Coding Agent 这种东西,本来就是高频调用,一旦启动慢几次,用户马上就会烦。
更关键的是,它不是拍脑袋优化。它还专门做了一个启动剖析器:
1constPHASE_DEFINITIONS={
2 import_time:['cli_entry','main_tsx_imports_loaded'],
3 init_time:['init_function_start','init_function_end'],
4 settings_time:['eagerLoadSettings_start','eagerLoadSettings_end'],
5 total_time:['cli_entry','main_after_run'],
6}asconst
也就是说,启动性能在 Claude Code 里不是“感觉有点慢,顺手改改”,而是有明确阶段、有采样、有统计的。这个习惯其实特别值得学:先度量,再优化。
很多工程最后做不细,往往不是因为大家不聪明,而是因为没有把问题变成可以持续被观测的东西。Claude Code 在这里的处理方式很典型,也很成熟。
它真正的灵魂,不是对话框,而是 Query Loop
如果只能挑一个文件代表 Claude Code 的“心脏”,我会选 query.ts。
原因很简单:这个文件暴露了它最真实的设计哲学。它不是那种“发请求给模型 -> 收到回复 -> 执行工具 -> 结束”的线性流程,而是一个持续运转、随时恢复、随时调整的状态机。
源码里直接定义了核心状态:
1typeState={
2 messages: Message[]
3 toolUseContext: ToolUseContext
4 autoCompactTracking: AutoCompactTrackingState |undefined
5 maxOutputTokensRecoveryCount:number
6 hasAttemptedReactiveCompact:boolean
7 maxOutputTokensOverride:number|undefined
8 pendingToolUseSummary:Promise<ToolUseSummaryMessage |null>|undefined
9 stopHookActive:boolean|undefined
10 turnCount:number
11 transition: Continue |undefined
12}
然后外层就是一个 while (true) 的大循环。
乍一看很朴素,但真正有价值的不是这个循环本身,而是它把一堆“原本很容易把 Agent 弄崩的情况”都吸收进来了:
-
上下文太长,触发 autocompact -
413 或媒体过大,触发 reactiveCompact -
输出 token 不够,进入恢复分支 -
stop hooks 阻塞,转成下一轮状态继续跑 -
工具执行结束,再回到下一个 turn
这套设计我特别喜欢的一点在于:
它不是把错误当异常处理,而是把错误当正常流程的一部分。
这一点特别重要。因为只要系统开始复杂,所谓“异常”其实一点都不异常。上下文爆掉,不异常;工具输出太大,不异常;中途要压缩历史,不异常;模型停在某个中间态,也不异常。真正糟糕的不是出问题,而是系统一出问题,整个会话逻辑就散了。
Claude Code 显然是很有意识地在避免这种事。
它甚至不只做一种压缩,而是把 snip、microcompact、contextCollapse、autocompact、reactiveCompact 这些东西分层放在一起。你会明显感觉到,开发者对“上下文怎么活下来”这件事非常在意。
如果用更口语一点的话说,我读到这里的感受是:别人是在调模型,Claude Code 是在经营一段长生命周期会话。
我越来越确信的一点:上下文,在 Claude Code 里就是生命线
这次读源码,一个特别强烈的感觉是,Claude Code 团队对“上下文”这件事是带着敬畏在做的。
在 query.ts 里,你会看到大量关于压缩和恢复的逻辑:
1const reactiveCompact =feature('REACTIVE_COMPACT')
2const contextCollapse =feature('CONTEXT_COLLAPSE')
3const snipModule =feature('HISTORY_SNIP')
后面又有这种注释味道很重的代码:
1// Apply snip before microcompact
2// Runs BEFORE autocompact
3// contextCollapse.recoverFromOverflow(...)
这说明什么?
说明在 Claude Code 里,上下文不是“消息数组”这么简单,而是系统里最宝贵、最脆弱、也最需要被精细管理的资源。
你想想就知道,一个 Coding Agent 跟普通聊天机器人最大的区别,不是它会不会写代码,而是它要长期记住:
-
你刚才做到了哪一步 -
哪些文件改过 -
哪些工具已经调用过 -
当前目标还剩什么 -
哪些历史其实已经不重要了
如果这些东西一股脑全塞着不管,迟早会炸;但如果压缩得太粗暴,又会把真正重要的信息压没。所以 Claude Code 做的事情,不是单纯“省 token”,而是在做上下文的分层治理。
这个点我非常认同。很多人以为 Agent 的核心挑战在模型,其实做久了就知道,另一个巨大挑战恰恰是:你怎么管理一段会越来越长、越来越乱、还必须保持可用的上下文。
从这个角度看,Claude Code 代码里最值钱的,未必是某一个 prompt,而是它围绕上下文建立出来的整套恢复机制。
工具系统做得像一个小型内核
再往下看 Tool.ts,会更明确地感觉到,Claude Code 的主角其实不是“回复文本”,而是“调度能力”。
工具在这里不是普通函数,而是平台级对象。每个工具都不只是“能执行”,还必须显式声明自己的特征:
1readonly inputSchema: Input
2isConcurrencySafe(input):boolean
3isEnabled():boolean
4isReadOnly(input):boolean
5isDestructive?(input):boolean
6prompt(options):Promise<string>
这套设计为什么好?
因为它把“工具是什么”从“一个方法”提升成了“一个可治理对象”。
一旦这层协议建立好了,权限系统可以围着它转,UI 可以围着它转,提示词系统可以围着它转,并发控制也可以围着它转。你接一个 Bash tool 进来,和接一个 MCP tool 进来,在平台层其实是同一种事情。
更有意思的是它的默认值:
1constTOOL_DEFAULTS={
2isEnabled:()=>true,
3isConcurrencySafe:(_input?:unknown)=>false,
4isReadOnly:(_input?:unknown)=>false,
5isDestructive:(_input?:unknown)=>false,
6}
这很有工程味:默认不认为你安全,默认不认为你是只读,默认不假设你可以乱并发。
换句话说,Claude Code 在工具层的原则不是“默认放开”,而是“默认收紧”。这种默认值看着保守,实际上对 Agent 系统特别重要,因为 Agent 一旦有执行权,保守一点远比大胆一点值钱。
它的并发也很有意思:不是盲目并发,而是保守并发
services/tools/toolOrchestration.ts 里有一段很能体现风格的逻辑:
1for(const{ isConcurrencySafe, blocks }ofpartitionToolCalls(...)){
2if(isConcurrencySafe){
3// 并发执行
4}else{
5// 串行执行
6}
7}
而且它不是“只要是读操作就并发”这么粗糙。它会先解析工具输入,再调用工具自己声明的 isConcurrencySafe(),只有在确认安全的情况下,才把一批工具归进并发通道。
这段代码其实特别能说明 Claude Code 的产品气质。
它追求的是“尽量快,但别乱”。这和很多 Demo 项目的做法很不一样。Demo 项目往往更偏展示效果,先让它能同时跑起来再说;Claude Code 这套则明显更在意副作用、上下文一致性和可恢复性。
说难听一点,激进并发很容易,代码也好写;保守并发反而难,因为你得先回答一个麻烦问题:到底哪些事情能一起做,哪些事情不能。
这活儿不炫,但非常产品化。
Bash Tool 让我看到的,不是“执行命令”,而是“管理命令生命周期”
tools/BashTool/BashTool.tsx 是我这次特别喜欢的一个实现。
它有两个地方很打动我。
第一,它会判断一条命令是不是搜索类、读取类命令:
1exportfunctionisSearchOrReadBashCommand(command:string){
2// 解析管道、操作符,判断是不是纯读操作
3}
这意味着 UI 不需要把 rg、cat、ls、tree 这些命令输出一视同仁,而是可以更聪明地折叠、摘要和展示。别小看这个细节,这其实是在把“命令执行”转成“可理解的交互对象”。
第二,它对慢命令的处理特别像操作系统调度,而不是 shell 包装器:
1constPROGRESS_THRESHOLD_MS=2000;
2constASSISTANT_BLOCKING_BUDGET_MS=15_000;
2 秒后显示进度,15 秒后 assistant 模式自动转后台。
这个设计很有真实世界经验。真正用终端的人都知道,很多命令不是错,只是慢。如果所有慢命令都硬卡在前台,那用户和 Agent 都会被锁住。Claude Code 的处理方式其实是在说:“正在做事”和“卡住不动”不是一回事。
只要任务还在推进,就应该允许它后台跑,让主线程继续保持交流和调度能力。
所以我会觉得,Claude Code 的 Bash Tool 已经不是简单的“执行 shell”,而是一个 Agent 场景下的命令生命周期管理器。
安全这件事,它也不是“弹个确认框”就完了
这次读源码,还有一个很明显的感受:Claude Code 的安全设计不是单点式的,而是纵深防御。
utils/permissions/permissionSetup.ts 里面直接就有这些函数:
1exportfunctionisDangerousBashPermission(...)
2exportfunctionisDangerousPowerShellPermission(...)
3exportfunctionisDangerousTaskPermission(...)
这几个命名一出来,你基本就知道它在防什么了。
它不是只防“执行 rm -rf”,而是在防更底层的问题:如果你给 Bash、PowerShell 或子 Agent 过于宽泛的权限规则,会不会等于把整个系统的护栏直接拆掉?
比如:
-
Bash 规则过宽,会不会放任脚本解释器执行任意代码 -
PowerShell 规则过宽,会不会放开 Invoke-Expression、Start-Process这种逃逸点 -
Agent 规则过宽,会不会让子 Agent 在没有评估 prompt 风险的情况下无限派生
我特别认同这种做法。很多系统一说安全,就是“要不要确认一下”;但真正的安全设计从来不是只在最后弹一下框,而是前面一层层收口:工具定义、默认权限、危险模式识别、自动模式剥离危险规则、工作区信任、文件系统边界……
Claude Code 在这方面给我的感觉很明确:它知道 Agent 的危险不只来自“模型乱说”,也来自“系统太宽松”。
文件读取这件小事,它都做得很老练
FileReadTool 也很能说明问题。
比如它会直接拦下设备文件:
1constBLOCKED_DEVICE_PATHS=newSet([
2'/dev/zero',
3'/dev/random',
4'/dev/urandom',
5'/dev/stdin',
6'/dev/tty',
7])
这类代码一看就不是“学院派会先想到的东西”,而是踩过坑的人才会认真写上的东西。因为你真的把这类路径读进去,轻则挂死,重则把会话拖垮。
它还会在文件超出 token 预算时抛出专门的错误,引导模型去用 offset 和 limit:
1exportclassMaxFileReadTokenExceededErrorextendsError{
2constructor(public tokenCount:number,public maxTokens:number){
3super(`File content exceeds maximum allowed tokens...`)
4}
5}
你看,重点不是“拒绝访问”,而是“告诉你下一步怎么正确读”。
这就是成熟产品和实验项目很大的区别。实验项目的逻辑经常是“你用错了,那就失败”;成熟产品则更像是“你用错了,我尽量把你带回正确路径”。表面上只是一个错误类,背后其实是完全不同的设计态度。
它还是一个终端前端应用,而且已经在认真做多智能体平台
很多人会忽略这一点:Claude Code 其实非常认真地把终端当成前端应用来做。
从 interactiveHelpers.tsx、components/、state/AppStateStore.ts 这些地方能明显看出来,它不是把终端当日志窗口,而是当应用容器。
它有完整的 AppState,有 Onboarding、有 TrustDialog、有通知队列、有 footer 状态、有前后台任务切换,甚至还有 bridge、remote、tmux 这类会话级能力。
再往 tools/AgentTool/loadAgentsDir.ts 里看,你会发现它对“Agent”本身也已经不是玩具级理解了:
1memory: z.enum(['user','project','local']).optional(),
2background: z.boolean().optional(),
3isolation: z.enum(['worktree','remote']).optional(),
4requiredMcpServers?:string[]
5omitClaudeMd?:boolean
这段代码很有意思。
它说明在 Claude Code 里,一个 Agent 不是“换个 prompt 再开一轮对话”那么简单,而是带着:
-
自己的记忆范围 -
自己的后台属性 -
自己的隔离方式 -
自己依赖的 MCP 资源 -
自己该不该继承完整项目上下文
这时候你再回头看那些 assistant/、coordinator/、tasks/、skills/ 目录,就更容易理解了。
Claude Code 其实已经在往“终端里的多智能体平台”方向走了。它不是只有一个大模型在前台说话,而是开始认真思考:不同角色的 Agent,应该怎么被定义、隔离、记忆、调度、回收。
读到最后,我脑子里只剩下三个判断
如果让我把这次读源码的感受压缩成三个判断,我会这么说。
第一,Claude Code 的核心不是 prompt,而是 runtime。真正撑起体验的,不是某一段神奇提示词,而是启动、状态、权限、工具、恢复、UI 这些东西拼起来的运行时。
第二,它把上下文当成生命线。从 snip 到 autocompact,从 contextCollapse 到 reactiveCompact,你能明显看出来,团队非常清楚:上下文不是越多越好,而是要活得久、活得稳、活得有层次。
第三,它的安全感来自系统设计,不来自一句“请小心”。真正靠谱的 Agent,不是最后多弹一个确认框,而是前面每一层都少放一点风险。
如果一定要再讲得更人话一点,那就是:
这套源码读下来,你会感觉作者不是在想“怎么让模型更聪明”,而是在想“模型没那么聪明的时候,系统怎么别崩”。
而我觉得,这恰恰是 AI 工程里最现实、也最值钱的部分。
结尾
让 Codex 帮我把 Claude Code 这份源码读下来之后,我最大的感受是:AI 编程产品竞争到后面,拼的已经不只是模型能力,而是谁先把“模型如何接入真实软件世界”这件事,做成稳定的基础设施。
Claude Code 的源码当然也不完美,main.tsx 这种超大文件本身就说明它还在持续演化。但整体方向已经非常清楚了:它不是在做一个会聊天的 CLI,而是在做一个能调工具、控权限、管状态、跑任务、接远程、撑长会话的终端智能体平台。
如果你也在做 Agent、做 AI CLI、做 Coding 产品,或者只是想知道“一个真正能用的 Coding Agent 到底需要多少工程托底”,那 Claude Code 这份源码真的非常值得读。
不是因为它完美,而是因为它很真实。你能从里面看到,一个产品在不断长大之后,工程系统是怎么一点一点把复杂度接住的。
当模型进入生产环境后,软件工程到底有多重要,这份源码给了一个非常具体的答案。
夜雨聆风