浩子的小站

深度揭秘 Claude Code:为什么它是最强大的 AI 编程助手?

字数统计: 4.9k阅读时长: 17 min
2026/04/03

基于 Claude Code v2.1.88 还原源码分析


前言

这篇是 Claude Code 系列里唯一保留的主稿,也是后续持续维护的最终权威版

如果你只想保留一篇 Claude Code 源码分析文章,看这篇就够了。

如果你把市面上的 AI 编程工具都用一遍,会很快发现一个区别:

有些工具第一回合看起来很惊艳,但任务一复杂、会话一拉长、工具一并发,就开始变钝、变乱、变得需要你不停接管。

Claude Code 不一样。

它不一定总是“最会炫技”的那个,但往往是那个最稳、最顺、最像能长期干活的系统

我最近把 Claude Code v2.1.88 的还原源码完整过了一遍。看完之后,我的判断比以前更明确了:

Claude Code 的优势,并不主要来自模型本身,而来自它把 Agent 做成了一套完整 runtime。

这篇文章我不再停留在“它很好用”的层面,而是回答三个更具体的问题:

  1. 它到底在哪些源码层面做得更好?
  2. 这些设计为什么会直接影响日常使用体验?
  3. 如果你也在做自己的 Agent,到底该抄它哪些作业?

如果你想把这套 runtime 思路继续放回自己的工具栈和系统设计里看,可以结合这两篇公开文章一起读:

版本边界

这篇分析基于 Claude Code v2.1.88 的还原源码。

有两类结论需要区分开来看:

  • 稳定结论
    • 运行时和交互层分离
    • 工具调度不是简单 tool calling
    • 上下文治理和权限链路是系统能力
    • 多代理和远程执行是明确产品方向
  • 可能随版本调整的细节
    • 某些文件路径和模块命名
    • hidden features / feature flags 的具体开关
    • compact、fallback、hooks 细枝末节的实现顺序

所以这篇文章更适合用来理解 Claude Code 的工程方法,而不是把每一个函数名都当成长期不变的接口。

先说结论:Claude Code 强在“运行时”,不是强在某个单点功能

很多人总结 Claude Code,会列一串功能:

  • 工具多
  • 命令多
  • 权限做得细
  • 会话更稳
  • 支持多代理

这些都对,但还不够准确。

更准确的说法是:

Claude Code 真正强的,是它把“工具调用、上下文治理、权限控制、异常恢复、多代理隔离”都收敛进了同一套运行时。

换句话说,它不是“模型 + 一堆工具”的拼装,而是已经很接近一个 Agent OS。

Claude Code 的运行时蓝图

如果你只盯着某个文件,比如最大的 query.ts,很容易看得很累。

更有效的阅读方式,是先建立这张脑内地图:

  • main.tsx 负责启动、预热、装配
  • QueryEngine.ts 负责 session lifecycle
  • query.ts 负责主循环
  • services/tools/* 负责工具调度和执行
  • services/compact/* 负责上下文治理
  • useCanUseTool.tsx 和 permissions 相关服务负责权限链路
  • AgentTool / coordinator/* / remote/* 负责多代理和隔离执行

这也是 Claude Code 和很多 Agent 工具最大的差别:
后者往往是“哪里需要逻辑就往哪里塞”,而它更像一套职责明确的系统。

如果你今天就要开始读这份源码,我建议不要一上来啃最大的文件,而是按“启动装配 -> 会话生命周期 -> 主循环 -> 工具执行 -> 治理链路”这条主线往下走。

Claude Code 源码阅读顺序

更具体一点:

  • 先看 main.tsx,搞清楚启动阶段到底装了哪些能力
  • 再看 QueryEngine.ts,理解一次 session 是怎么被收口成统一入口的
  • 然后进 query.ts,看真正的 agent loop 怎样处理流式输出、工具调用和恢复逻辑
  • 接着顺着 StreamingToolExecutor / toolOrchestration.ts 往下,理解它为什么能在复杂任务里保持工具执行秩序
  • 最后再看 compact 和 permissions 相关代码,理解它为什么不会越聊越乱、越跑越危险

这个阅读顺序的好处是,你会先建立系统模型,再去看实现细节。否则很容易一头扎进 query.ts 以后,只看到大量分支,却看不清这些分支为什么存在。

如果你更希望按“看一个文件,拿走一个结论”的方式来读,可以按下面这张表走:

源码入口 它主要负责什么 为什么先看它 看完应该得到什么结论
main.tsx 启动装配、预热、入口汇总 最适合先建立全局感 Claude Code 不是“命令行包一层模型”,而是先把系统装起来
QueryEngine.ts session lifecycle 这是会话真正的承载层 UI、SDK、headless 并不是三套实现
query.ts 主代理循环 这里能看到真正的执行主线 一个回合不是一次模型请求,而是一段 runtime 驱动的 loop
toolOrchestration.ts 工具批次编排 最容易看出它为什么更稳 工具并发不是 prompt 决定,而是运行时语义决定
StreamingToolExecutor 流式工具执行状态机 这是复杂任务稳定性的关键 queued / executing / yielded / completed 不是装饰性状态
services/compact/* 长会话治理 最能解释“为什么不会越聊越乱” 上下文在 Claude Code 里是受控资源
permissions 相关代码 allow/deny/ask 与 hooks 最能看出它为什么“既不放任,也不烦人” 权限不是弹窗,而是一条统一判定链

一、它不是把模型塞进终端,而是把会话生命周期从 UI 里抽了出来

这是 Claude Code 最值得学的一点。

很多 Agent 工具一开始就把自己写死在 UI 或 CLI 交互层里:

  • 输入框状态在组件里
  • 工具执行在某个回调里
  • 权限确认是个弹窗逻辑
  • 会话恢复是另一个补丁

短期能跑,但一旦你要支持:

  • Headless 模式
  • SDK 接入
  • 子代理
  • 远程执行
  • 自动恢复

整个结构就会开始打架。

Claude Code 的解法是:先定义运行时,再定义交互层。

源码里最关键的分工就是:

  • main.tsx 负责启动阶段的装配和预热
  • QueryEngine.ts 负责把一次会话的生命周期串起来
  • query.ts 负责进入真正的 agent loop

这背后的实际价值非常大。

因为它意味着:

  • UI 不承担核心业务状态
  • REPL 和 SDK 复用同一套执行路径
  • 工具调用、消息回写、权限拒绝、压缩、fallback 都能挂在统一 runtime 上

从工程上说,这几乎就是一句话:

不要把 Agent 写成“聊天界面调模型”,要把它写成“交互界面调用运行时”。

如果你自己在做 Agent,这是一条优先级极高的建议。
因为一旦这层没抽干净,后面加再多能力都会越来越乱。

二、Claude Code 真正拉开差距的,不是工具数量,而是工具调度系统

很多 Agent 工具都会宣传:

  • 能读文件
  • 能改文件
  • 能执行命令
  • 能联网

但真正复杂任务里的问题从来不是“有没有工具”,而是:

  • 哪些工具能并发?
  • 哪些工具必须串行?
  • 某个工具失败后,兄弟任务还要不要继续?
  • 用户打断后,是取消还是等待安全收尾?
  • 工具结果该不该立刻写回上下文?

Claude Code 在这件事上做得非常重。

一次请求如何穿过 Claude Code

如果把这条链路再说得更具体一点,一次真实请求大概是这样跑的:

  1. 用户在 CLI / REPL / SDK 里发出请求
  2. main.tsx 负责把启动阶段已经准备好的能力接进来
  3. QueryEngine.ts 接管这次 session,整理消息、状态和 abort controller
  4. query.ts 进入主代理循环,开始处理模型输出、tool use 和消息回写
  5. 如果模型开始发工具调用,toolOrchestration.ts 先判断批次和并发安全性
  6. StreamingToolExecutor 再负责执行中的状态推进、取消、兄弟任务终止和结果回写
  7. 同时 permissions / compact / fallback 等治理逻辑不断在旁边兜底
  8. 最终结果再被整形成用户看到的回复、UI 更新或新的工具输入

这条链路最值得注意的不是“步骤多”,而是每一步都被一个明确模块接住了。这也是为什么它在复杂任务里不容易因为某个临时补丁把整条链路搞乱。

1. 批次级调度,不是简单顺序执行

services/tools/toolOrchestration.ts 里,你能看到一个很关键的思想:

工具调用先被按语义分批,再决定执行顺序。

核心判断是 isConcurrencySafe

这意味着 Claude Code 并不是默认:

  • 全部串行
  • 或全部并发

而是会先看工具的副作用语义:

  • 读操作、查询操作,更适合并发
  • 写操作、有状态修改的操作,更适合串行

很多工具做不稳,不是因为模型笨,而是因为它们把“调度”问题错误地交给了模型自己解决。

Claude Code 没这么做。
它把“工具是不是并发安全”上升成了运行时知识。

2. StreamingToolExecutor 处理的是“执行中的世界”,不是“执行前的想象”

更关键的一层,在 StreamingToolExecutor

这里真正处理的是一个更现实的问题:

模型的输出是流式的,工具调用也是动态到达的。
所以运行时要处理的不是“拿到一组完整工具调用后怎么执行”,而是:

  • 工具边到达边进入队列
  • 有的已经开始执行
  • 有的还在等待
  • 有的可能需要取消
  • 有的已经完成但不一定该立即暴露

源码里能看到它明确区分这些状态:

  • queued
  • executing
  • completed
  • yielded

同时还处理:

  • sibling abort
  • interrupt behavior
  • fallback 时丢弃未完成结果
  • 某个工具失败时如何传播取消

这已经不是“tool calling”,而是接近一个 action scheduler。

如果你自己做 Agent,我会建议你优先补两层:

  1. 工具语义声明
  2. 运行时调度器

不要幻想 prompt 自己就能把这件事解决掉。

三、长会话为什么不容易崩:它把上下文当成受控资源来治理

很多 Agent 第一次用都不错,真正拉开差距的是第十次、第二十次回合以后。

这时会暴露两个问题:

  • 上下文越来越乱
  • 错误恢复越来越差

Claude Code 在这块下了很重的功夫,而且不是简单一句“超长了就总结一下”。

Claude Code 如何避免长任务失控

services/compact/* 相关代码里,你能看到它至少做了这些事情:

  • 估算有效上下文窗口
  • 为 compact summary 预留 token
  • 设置 warning threshold / error threshold / blocking limit
  • 区分 reactive compact、micro compact、trim compact 等不同处理方式
  • 在连续失败时熔断,避免 session 无限重试

这套设计最值得学的地方在于:

它把上下文溢出视为系统故障,而不是普通异常。

这和很多 Agent 工具的思路完全不同。

后者的逻辑通常是:

“超长了?那就随便总结一下吧。”

Claude Code 的逻辑更像:

“上下文是受控资源,需要像内存一样治理。”

这也直接解释了为什么它在长会话里更稳。
不是因为模型突然更聪明,而是因为运行时在持续控制上下文债务。

这里再补一个异常处理的例子,会更容易理解它为什么像“系统”,而不是“会聊天的模型壳子”。

假设一次回合里模型同时发起了几组工具调用,其中某个写操作失败了。普通 Agent 很容易出现几种糟糕状态:

  • 失败已经发生,但其他兄弟工具还在继续写
  • 中间结果已经回写给模型,导致上下文污染
  • 用户此时打断,运行时却不知道该取消哪些任务
  • 为了“自愈”不断重试,最后把 session 弄得更乱

Claude Code 在这类问题上的处理思路明显更成熟:

  • 工具先按并发安全语义分组,而不是默认同时跑
  • StreamingToolExecutor 会处理 sibling abort,让失败能正确传播给兄弟任务
  • fallback 期间会丢弃不该继续暴露的未完成结果
  • compact 连续失败后会熔断,而不是假装还能无限自救

换句话说,它不是寄希望于“模型自己别犯错”,而是承认复杂执行一定会出错,然后把错误当成运行时的一等公民来处理。

四、权限系统为什么“既不放任,也不烦人”

很多 Agent 工具在权限这件事上会走两个极端:

  • 全放开,结果很危险
  • 全询问,结果很烦

Claude Code 比较成熟的地方在于,它没有把权限做成一个 UI 弹窗功能,而是做成一条运行时链路。

源码里至少可以看到这些层:

  • allow / deny / ask 规则
  • 工具粒度匹配
  • MCP server 级匹配
  • sandbox override
  • working directory 限制
  • hooks 决策
  • classifier 辅助决策

useCanUseTool.tsx 则把这整套链路真正串起来,并根据运行模式区分:

  • 交互式会话怎么处理
  • 子代理怎么处理
  • swarm / coordinator worker 怎么处理
  • headless / SDK 怎么处理

这背后的启发很明确:

权限不是“操作前问一句”,而是“运行时是否允许进入下一步”的统一判定。

如果你把权限只做成弹窗,后面一旦要支持后台代理、远程代理、多代理协作,整个权限体系很容易立刻崩掉。

五、最有指导意义的一点:它已经在为“真正的软件代理”做地基

如果你继续往下看,就会发现 Claude Code 早就不只是一个本地 CLI。

源码里能看到非常多信号:

  • AgentTool 支持 background / teammate / remote / worktree 等不同执行后端
  • coordinator/* 在处理多代理协作
  • remote/* 在处理远程运行
  • plugins / skills / MCP 形成分层扩展体系
  • main.tsx 启动阶段已经在预取远程托管配置和能力注册信息

最关键的是,它并不是把这些能力都塞进一个“大插件系统”。

它的做法更像分层装配:

  • commands 解决用户入口
  • tools 解决能力执行
  • skills 解决可复用工作流
  • plugins 解决本地扩展
  • MCP 解决外部能力接入

这套分层有两个直接好处:

  1. 用户入口清晰,不会所有能力都混成一种东西
  2. 不同能力类型可以走不同的安全与权限策略

这也是为什么我会说:Claude Code 不是在做一个“更强的聊天助手”,而是在做一个软件代理基础设施

六、如果你也在做 Agent,最值得先抄哪几块作业?

这是看完源码以后,我觉得最有现实意义的部分。

因为大多数团队并不需要一次性做出 Claude Code 的全部能力,但非常值得先抄这四块。

做自己的 Agent 该先抄哪四块作业

1. 先把运行时和交互层分开

第一优先级,不是多加一个工具,而是把:

  • 会话生命周期
  • 工具调用
  • 消息回写
  • 权限判定
  • 上下文治理

从 UI 里抽出来。

如果这一步不做,后面一旦加 Headless、自动化、子代理,代码会迅速失控。

2. 给工具加“语义”,不要只加 schema

很多团队做工具时,只定义:

  • 名称
  • 入参
  • 出参

但真正决定稳定性的,是工具的运行时语义,比如:

  • 是否可并发
  • 是否有副作用
  • 是否可以打断
  • 失败后是否影响兄弟任务

Claude Code 强就强在这里。
它不是只知道“怎么调用工具”,而是知道“该怎么运行工具”。

3. 把上下文当成资源管理问题

不要把上下文管理理解成“写个总结 prompt”。

更稳的做法是:

  • 先算预算
  • 再设阈值
  • 然后定义 warning / blocking / compact 策略
  • 最后加失败熔断

这会极大改善长会话稳定性。

4. 权限一定要走统一判定链

如果未来要支持:

  • 子代理
  • 远程执行
  • 自动化 worker

那权限系统必须是一条统一 runtime 链路,而不是各处散落的 if / confirm。

这件事越晚补,成本越高。

七、如果和普通 Agent 实现对照,Claude Code 到底领先在哪

如果把它和很多常见实现放在一起对照,差异会更直观:

常见 Agent 写法 Claude Code 的写法 实际收益
UI 组件里直接持有主要会话状态 QueryEngine.ts 抽出独立 session lifecycle 更容易支持 SDK、headless、多代理
工具调用按模型输出顺序直接执行 先做语义分批,再进流式执行器 复杂任务里更稳,不容易互相踩
上下文超了就“总结一下” warning / blocking / compact / 熔断 长会话不会无限失控
权限是零散确认框 allow / deny / ask + hooks + 限制链路 安全边界更清晰,交互也更顺
多代理像附加功能 AgentTool / coordinator / remote 是一层体系 能持续往“软件代理系统”方向长

这张表也解释了为什么我一直说:Claude Code 真正领先的不是某个功能,而是它已经把很多“迟早要补的系统能力”提到前面做了。

八、隐藏功能值得看,但它们不是 Claude Code 最核心的竞争力

源码里确实还能看到很多有意思的东西:

  • Undercover mode
  • Buddy 宠物系统
  • KAIROS 自主代理模式
  • 语音模式
  • 远程托管设置
  • 大量 feature flags

这些都说明 Claude Code 的产品边界远比表面看起来大。

但如果只把注意力放在这些“彩蛋”上,会错过真正重要的部分:

Claude Code 最核心的竞争力,不是它藏了多少功能,而是它已经把 Agent 的基本盘做对了。

也就是:

  • runtime 抽象
  • 工具调度
  • 上下文治理
  • 权限链路
  • 多代理隔离

这些才是决定它为什么能长期稳定工作的东西。

最后一句

如果你今天还把 Agent 理解成“一个会聊天、会写代码的大模型外壳”,那你看到的还是第一阶段。

Claude Code 源码真正让我确认的一件事是:

下一代 Agent 的竞争,拼的已经不是“谁更会回答”,而是:

  • 谁更像一个运行时
  • 谁更像一个系统
  • 谁更能在复杂任务里保持稳定、可控、可扩展

Claude Code 之所以更好用,不是因为它更会说,
而是因为它更会工作。


源码信息

  • 分析版本:Claude Code v2.1.88
  • 代码量:约 163,000 行 TypeScript
  • 文件数:1,884 个文件
  • 主要语言:TypeScript 6.0+ / Bun
  • 终端 UI:React + Ink

源码仓库:

注:本文仅供学术研究与教育目的。使用者应自行遵守相关法律法规及服务条款。

CATALOG
  1. 1. 深度揭秘 Claude Code:为什么它是最强大的 AI 编程助手?
    1. 1.1. 前言
      1. 1.1.1. 版本边界
    2. 1.2. 先说结论:Claude Code 强在“运行时”,不是强在某个单点功能
    3. 1.3. 一、它不是把模型塞进终端,而是把会话生命周期从 UI 里抽了出来
    4. 1.4. 二、Claude Code 真正拉开差距的,不是工具数量,而是工具调度系统
      1. 1.4.1. 1. 批次级调度,不是简单顺序执行
      2. 1.4.2. 2. StreamingToolExecutor 处理的是“执行中的世界”,不是“执行前的想象”
    5. 1.5. 三、长会话为什么不容易崩:它把上下文当成受控资源来治理
    6. 1.6. 四、权限系统为什么“既不放任,也不烦人”
    7. 1.7. 五、最有指导意义的一点:它已经在为“真正的软件代理”做地基
    8. 1.8. 六、如果你也在做 Agent,最值得先抄哪几块作业?
      1. 1.8.1. 1. 先把运行时和交互层分开
      2. 1.8.2. 2. 给工具加“语义”,不要只加 schema
      3. 1.8.3. 3. 把上下文当成资源管理问题
      4. 1.8.4. 4. 权限一定要走统一判定链
    9. 1.9. 七、如果和普通 Agent 实现对照,Claude Code 到底领先在哪
    10. 1.10. 八、隐藏功能值得看,但它们不是 Claude Code 最核心的竞争力
    11. 1.11. 最后一句
    12. 1.12. 源码信息