章节导航 第 2 章 / 共 23 章
工程结构全景
302 个目录、1,903 个文件的代码地图——五层架构、枢纽文件、模块依赖关系的完整拆解
2.1 从一张结构轮廓图开始
在深入任何细节之前,先看清全局。Claude Code 的核心代码全部位于 src/ 目录下,按功能划分为 35 个顶层子目录。下面这张流程图描绘了从启动到执行的主要模块关系:
main.tsx (主入口)
├── entrypoints/init.ts + setup.ts (启动初始化)
│ └── commands.ts + tools.ts (注册层)
│ └── 运行模式分流
│ ├── screens/REPL.tsx (交互式 REPL)
│ ├── cli/print.ts (Headless/SDK)
│ ├── bridge/bridgeMain.ts (IDE Bridge)
│ └── entrypoints/mcp.ts (MCP Server)
│
└── 所有模式最终进入
QueryEngine.ts + query.ts (会话引擎)
└── services/* + utils/* (基础设施)
这张图揭示了一个关键设计决策:所有运行模式最终汇聚到同一个会话引擎(QueryEngine.ts)。REPL、Headless、Bridge、MCP 只是不同的入口壳,核心的 Agent Loop 逻辑是共享的。
Dynamic Workflows GA(2026-07-02)引入了一个例外:编排脚本运行在独立的 JavaScript 运行时中,不经过 QueryEngine 的主循环。子 agent 通过编排引擎间接与 QueryEngine 交互,编排逻辑与主循环完全解耦。这形成了事实上的’编排引擎层’——在入口层和 QueryEngine 之间的一层,负责任务分解、并发控制和结果聚合。
源码引用:
src/main.tsx是主入口编排器,负责 CLI 参数解析、初始化和模式分流。参见源码分析知识库01-源码结构与技术栈分析.md。
2.2 五层架构
Claude Code 的 35 个子目录可以映射到五个逻辑层,依赖方向单向向下:
入口层(Entrypoints)
| 文件/目录 | 角色 | 说明 |
|---|---|---|
main.tsx | 主进程入口 | CLI 参数解析、预取、初始化、模式分流 |
entrypoints/ | 共享入口 | 8 个文件:init.ts(全局初始化)、cli.tsx(CLI 模式)、mcp.ts(MCP 模式)、agentSdkTypes.ts(SDK 类型) |
setup.ts | 会话准备 | cwd 解析、worktree 初始化、tmux 集成、hooks 加载、终端恢复 |
入口层的核心职责是模式判断与环境准备。main.tsx 根据命令行参数和环境变量决定走 CLI、MCP Server 还是 SDK 模式,然后将控制权交给对应的入口点。
值得注意的是 main.tsx 是一个”超大单文件”——这不是代码风格问题,而是有意为之。启动流程需要按严格顺序执行(参数解析 → 预取 → 安全检查 → 初始化 → 模式分流),拆分成多个文件反而会增加执行顺序的不确定性。
受限运行时:正交于入口选择的加载期维度(v2.1.248,2026-08-27)
以上讨论回答的是”从哪个入口进入”。v2.1.248 引入了一个不属于入口层的变量——--restricted(或 CLAUDE_CODE_RESTRICTED=1)。官方 changelog 原文:
Added
--restricted(orCLAUDE_CODE_RESTRICTED=1): removes the built-in tools that run commands or code andWebFetch(unless named in--tools), keeps file tools inside the working directory, refusesbypassPermissions, and ignores user, project and local settings files
它的作用位置在入口之后、工具池装配之前:不改变”从哪个入口进”,而改变”哪些层被加载”。四条约束分别落在不同层——工具层(移除运行命令或代码的内置工具与 WebFetch,除非在 --tools 中显式命名)、文件作用域(文件工具保留在工作目录内)、权限层(拒绝 bypassPermissions)、配置层(忽略用户、项目与本地设置文件)。对 2.1 节那张轮廓图的影响是:所有模式仍然汇聚到 QueryEngine.ts,但汇入时可调用的工具体已被裁剪过一轮。第 5 章会展开它在工具池装配中的位置,第 7 章会展开它与权限模型的交叉。
插件目录热插拔:装配期之后的增量通道(v2.1.265,2026-09-08)
--restricted 决定的是”启动完成时这一帧里有哪些层”。v2.1.265 给出了另一个方向上的变量——官方 changelog 原文:
Added support for pointing
--plugin-dirat a folder of plugins: each child folder with a manifest loads, and children added or removed while running are picked up
它不新增一层,而是给已有的注册层加了热插拔入口:entrypoints/init.ts + setup.ts → commands.ts + tools.ts 这条一次性注册链路之外,多了一条”运行中增量装配”通道——插件目录里带 manifest 的子文件夹在运行中被增删会被拾取。这与 2.1 节 Dynamic Workflows 那个例外属于同一类:装配期之外还有一个增量装配通道。
两个变量放在一起看,本章那张结构图的定位就更清楚了:它描述的是启动完成时的那一帧。--restricted 决定这一帧里有哪些层,--plugin-dir 决定这一帧之后还能往里加什么。
⚠️ 后续补丁值得记一笔:v2.1.281 修了一个反直觉的 bug——--plugin-dir 指向一个内含 .claude-plugin/marketplace.json 的插件文件夹时,只会加载一个空插件而不是里面的插件(Fixed --plugin-dir on a folder of plugins that also has a .claude-plugin/marketplace.json loading one empty plugin instead of the plugins in it)。“文件夹既能是插件集合又能是市场”这件事,本身就是这条热插拔通道引入的歧义。
扩展层(2026-10-17 新增):第四种扩展机制 Mods
入口层那两个变量回答的是”这一帧里有什么、之后还能加什么”。v2.1.287(2026-10-01)之后,这个问题多了一个维度:这一帧是怎么被处理出来的。 官方 changelog 逐字写 “Added Claude Mods: plugins may now modify deeper behavior”。
四种扩展机制各有各的改动对象(官方 Mods overview 的对照,可直引):
| 扩展方式 | 主要改变什么 | 适用 |
|---|---|---|
| Skills | 模型读到的指令与作业流程 | 教它 recurring 规则或专门任务 |
| MCP servers | 可用的外部工具 | 接工单系统、数据库等 |
| Settings Hooks | 事件上跑命令 / HTTP | 用既有脚本做拒绝、放行、记录 |
| Mods | 内部事件的处理流,以及界面 | 加自定义面板、改写处理、用自定义响应替代正常处理 |
三条限定必须连着写:
- Mods 改的不只是这一帧的内容,是这一帧的处理过程。 官方公告逐字列出一个 mod 可以:rewrite a prompt before it reaches the model、block / rewrite / retry a tool call、approve or deny a permission request、redact secrets from tool output before Claude reads it、编辑或替换界面的一部分,并替换内置功能(内置
/diff现已是 mod,官方称 “We plan to move more built-in features to mods over time”)。与 Hooks 的分界官方也给了原话:hooks “can’t rewrite events, draw new UI, or replace features”,mods 可以。 - 多个 mod 挂在同一个事件上时按加载顺序执行,先加载的先见事件、最后见结果——这是一条中间件链。因此加载顺序是安全属性,不是实现细节:谁先看到事件,谁就能决定后面的人看到什么。
- 🔴 Mods 不在沙箱内。 官方公告逐字:“Mods run with the same access to your machine as Claude Code itself. They aren’t sandboxed, and you should only install mods from sources you trust.” 企业侧的约束手段是顺序约定:Team / Enterprise 计划及任何有托管设置的机器上,内置
sec-default(“security default”)mod 首个加载,阻止用户安装的 mod 覆盖 permission deny 规则;管理员可以改为先加载自己的 mod,官方提示此时要把sec-default一并加回列表。这一条把 Ch01 讨论的”工具供应链安全”从”工具本身是不是坏的”推进到”扩展层本身是不是可信的”。
⚠️ 尚未回源的两点,写入时不扩写:mod 的完整事件名清单(不同来源对 tool.call / prompt.submit / turn.step / ui.render 的拼写需以官方 Mods 参考页为准);sec-default 的完整限制清单(官方称可查看源码,本轮未读源码,只记官方明示的那一条)。
命令层(Commands)
| 目录 | 规模 | 说明 |
|---|---|---|
commands/ | 207 个文件,26,528 行 | Slash 命令实现 |
commands.ts | 枢纽文件 | 命令注册中心 |
先分清两套命令系统。上表统计的 207 个文件实现的是会话内 Slash 命令(/xxx),运行在已启动的会话里,由 commands.ts 注册中心装配。另有一层是进程级 CLI 子命令(claude xxx),管理会话自身的生命周期,不进入会话内部。v2.1.251(2026-08-28)把后者的清单写进了 claude --help:
Added
attach,logs,stop,respawn, andrmtoclaude --help; the--resumemessage for a running background session now names the exactclaude attach <id>command
新增的五个子命令(attach、logs、stop、respawn、rm)操作的都是后台会话进程:--resume 遇到仍在运行的后台会话时,现在会直接指名确切的 claude attach <id> 命令。区分这两层的用处在于:第 8 章的多 Agent 协作管理子代理用的是跨进程的生命周期命令,而不是会话内 Slash 命令。
进程级子命令的第六个成员,也是第一个不操作会话进程的(v2.1.269,2026-09-11):官方 changelog 原文——
Added
claude plugin eval: run a plugin’s eval suite against Claude Code and get scored, reproducible results (JSON + HTML report); seeclaude plugin eval --help
attach/logs/stop/respawn/rm 五个管的都是”会话进程还在不在、怎么接回去”,claude plugin eval 管的是插件的质量。它把”进程级子命令”的职责从生命周期管理扩展到了质量工程:跑一个插件的 eval 套件,产出可复现的 JSON + HTML 报告。官方 plugin-evals 文档给出的机制值得抄进任何插件工程实践:
claude plugin eval init开一个交互会话,问你”什么算好结果”、设计 grader、试跑一次,然后把每个 case 写成一个目录- 默认每个 case 跑 6 次:带插件 3 次 + 不带插件 3 次,输出 WITH / W/OUT / Δ 三列——Δ 才是插件真正的贡献
- 6 种 grader:
regex/tool_used/tool_order/file_exists(纯本地,不花钱)+llm/baseline(调裁判模型,计入成本) - 最常见的首次发现是 Δ≈0 且
tool_used: Skillgrader 失败——Claude 在自然措辞下根本没选中你的 skill,修法是改 skill 的 description 而不是改逻辑
⚠️ 版本号以官方为准:本轮检索中有第三方把 claude plugin eval 写成 “shipped in v2.1.271(September 14)“,官方 changelog 明确是 v2.1.269(September 11)。这本书此前已经反复遇到”第三方版本号滞后一整轮”,这次是错号而非滞后——同一类问题,但更隐蔽。另:v2.1.283 起该命令要求 git 2.31 或更高(装了 git 的话),低版本会被拒绝并报出版本号。
两条后续演进把这条质量工程线再推一步(2026-10-17):
- v2.1.290 —— hook 失败会不会放行,变成可检查项。
claude plugin validate现在列出 mod 在每个 gating site 注册的 hook 及其有没有.catch(--json下字段名为gatingHooks)。这与 v2.1.295 的onFailure: "block"是一对:前者能检查,后者能声明。放在一起看,官方给的是一套完整的”钩子自身失败”工程闭环——先能声明期望行为,再能静态检查有没有声明。 - v2.1.299 —— Δ 更干净。
claude plugin eval新增--disallow-tools与 case 字段disallowed_tools,可把Agent等内置工具排除在 eval 之外。官方 changelog 的说明是 “keep specific built-in tools, such as Agent, out of eval runs”——排除子代理兜底之后测出来的,才是插件自身的贡献。这也把 Δ 三列机制的一个隐含前提摆到了台面上:Δ 干净与否取决于对照组里排除了什么,而”排除什么”此前是不可配置的。
命令层实现了 70+ 内置 Slash 命令,按功能分为:
| 分类 | 主要命令 |
|---|---|
| 基础命令 | /help、/config、/model、/version、/login、/logout |
| Git 操作 | /commit、/diff、/status、/issue、/pr_comments |
| UI 交互 | /theme、/color、/keybindings、/vim |
| 系统管理 | /doctor、/memory、/tasks、/skills |
| 集成扩展 | /mcp、/plugins、/oauth-refresh |
源码引用:
commands.ts通过 Feature Flags 实现命令的动态加载,部分命令根据 Flag 条件导入。 参见:commands.ts
工具层(Tools)
| 目录 | 规模 | 说明 |
|---|---|---|
tools/ | 184 个文件,50,863 行 | 42+ 工具实现 |
Tool.ts | 枢纽文件 | 工具契约中心——定义 Tool Schema、权限上下文、执行上下文 |
tools.ts | 枢纽文件 | 工具注册中心——工具列表装配与环境裁剪 |
工具层是 Agent 的”手脚”。42+ 内置工具横跨文件操作、代码搜索、命令执行、任务管理、MCP 集成、多代理协作等 9 大类(完整分类见第 5 章)。
工具清单会收缩,收缩路径是”先合并、再弃用、再移除”。 本章给的是一个静态快照,而工具层是五层里 churn 最快的一层。TaskOutput 工具的完整生命周期就是现成的标本:
| 版本 | 动作 | 官方 changelog 原文要点 |
|---|---|---|
| v2.0.64 | 合并 | AgentOutputTool 与 BashOutputTool 下线,合并为统一的 TaskOutputTool |
| v2.1.83 | 弃用 | 改为用 Read 读后台任务的输出文件路径 |
| v2.1.277 | 移除 | 工具本体删除;taskOutputMaxChars 设置与 TASK_MAX_OUTPUT_LENGTH 环境变量一并失效 |
📌 这也是”42+“这个写法值得保留的原因:写死成”42 个工具”的话,v2.1.277 之后这一行就是错的。模糊上限比精确数字更耐久——对逆向研究类写作来说,这是一条通用的表述策略。
2026-09 ~ 10 出现了第四条收缩路径:改形(reshape)。 工具既不被合并也不被移除,而是改变它的输入 / 输出形态:
| 版本 | 工具 | 改了什么 |
|---|---|---|
| v2.1.285 | WebFetch | CLAUDE_CODE_DISABLE_WEB_FETCH 环境变量可把该工具直接关掉(官方 changelog 逐字 “turn off the WebFetch tool”) |
| v2.1.296 | Read | 新增 allow_large 选项,需要全文且上下文放得下时,可一次读完超过常规体积限制的文本文件 |
| v2.1.299 | @ 文件提及 | CLAUDE_CODE_DISABLE_FILE_MENTIONS:Claude 只拿到 @ 提及文件的路径去用自己的文件工具读,而不是文件内容 |
把它与入口层的 --restricted 对照着看,粒度变化最清楚:--restricted 是启动期、整层、减法(移除命令执行类工具与 WebFetch);这三条是运行期、单工具、改形(关掉一个工具 / 放开一次读取上限 / 把内容换成路径)。工具层的裁剪粒度从”整层”下沉到了”单个工具的输入输出”,而且裁剪动作不再是单向的减法——allow_large 是加法。所以”工具清单会收缩”这句话要改成:工具清单会收缩,也会变形。
工具产出不是无限写入的。 v2.1.265(2026-09-08)给单次工具结果落盘加了显式配额,官方原文:
Added a 1 GB cap on tool results saved to disk; the in-conversation preview says when a saved file was truncated
注意后半句:会话内预览会明确告诉你文件被截断过。工具层第一次有了”我给你的结果不完整”的自述通道。
每个工具通过 Tool 接口定义,包含关键属性:
// Tool.ts 中的工具定义接口(简化)
interface Tool {
name: string;
description: string; // 工具描述(给模型看的)
inputSchema: ZodSchema; // Zod Schema 运行时验证
call(input): Promise<ToolResult>; // 执行逻辑
// 行为标记
isConcurrencySafe: boolean; // 是否并发安全(默认 false)
isReadOnly: boolean; // 是否只读(默认 false)
isDestructive: boolean; // 是否破坏性操作
shouldDefer: boolean; // 是否延迟加载
alwaysLoad: boolean; // 是否始终加载
// 权限与安全
checkPermissions(input): PermissionResult;
toAutoClassifierInput(): string; // 给 AI 分类器的输入
}
设计亮点:
buildTool函数采用 fail-closed 策略填充默认值——isConcurrencySafe默认false(假设不安全),isReadOnly默认false(假设会写入)。这意味着忘记设置某个标记时,系统会选择更安全的行为。参见:Tool.ts
服务层(Services)
| 目录 | 规模 | 说明 |
|---|---|---|
services/ | 130 个文件,53,683 行 | 18+ 服务模块 |
服务层是业务逻辑的核心,包括:
- API 服务:与 Anthropic API(以及 Bedrock/Azure/Vertex 后端)的通信
- MCP 服务:MCP 客户端管理、配置解析、传输层(stdio/SSE/HTTP/WebSocket)、描述长度配额——⚠️ 自 v2.1.295 起这个配额不再是一个全局默认值,而是按加载路径分叉的四条轨道(详见 2.7「体积三面」表后的四轨表):会话级总开关
CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH(v2.1.280,原默认 2,048 字符/server)、经 tool search 加载的工具描述 16,384(v2.1.295)、sent up front 的工具描述与 server instructions 4,096(v2.1.296)、Anthropic 第一方 server(api.anthropic.com)不截断(v2.1.299) - 权限服务:权限规则解析、决策引擎、AI 分类器集成
- 遥测服务:使用量统计、性能监控、错误上报
- LSP 服务:语言服务器协议集成
- OAuth 服务:认证流程管理
- 策略服务:速率限制、Token 预算、安全策略
- 工具执行服务:
toolExecution.ts——工具调用的完整生命周期管理,包括权限验证、流式执行、错误分类。“错误分类”在 v2.1.265 之后多了一类:结果过大被截断(详见工具层小节)——它既不是权限拒绝也不是执行失败,而是”执行成功但产出被裁剪”
组件层(Components + UI)
| 目录 | 规模 | 说明 |
|---|---|---|
components/ | 389 个文件,81,892 行 | React 终端 UI 组件 |
ink/ | 96 个文件,19,859 行 | Ink 框架定制实现 |
hooks/ | 104 个文件,19,232 行 | React Hook 与交互逻辑 |
screens/ | 3 个文件,5,980 行 | 全屏界面(REPL、Doctor、Resume) |
Claude Code 使用 React + Ink 构建终端 UI。Ink 是一个将 React 组件渲染到终端的框架。但 Claude Code 并不是简单地使用了 Ink——src/ink/ 目录有近 2 万行定制代码,说明它对 Ink 做了大量内部改造。
screens/REPL.tsx 是交互式主界面,一个 5,980 行的组件(分布在 3 个文件中),集成了会话管理、权限控制、远程连接等核心逻辑。
2026-06 新增:展示层(Artifacts):2026-06-18 上线的 Artifacts(Team/Enterprise Beta)引入了一个新的输出维度——将终端会话转化为交互式 HTML 网页(私有 claude.ai URL),实时更新。Artifacts 不严格属于五层架构中的任何一层:它既不是入口(不接收用户输入),也不是终端组件(输出目标是浏览器)。从架构视角,它代表了一个平行的’展示层’——将 Claude Code 的会话产出从终端文本扩展为 web 可访问的可视化产物。16 MiB 渲染上限,严格 CSP 禁止外部请求,仅限 Org 内访问。
2.3 十个枢纽文件
在 1,903 个文件中,有 10 个文件的地位特殊——它们是整个系统的关节。理解了它们,就理解了 80% 的架构。
| 文件 | 角色 | 重要性 |
|---|---|---|
main.tsx | 主入口编排器 | CLI 参数解析、初始化、模式分流 |
screens/REPL.tsx | 交互式主界面 | REPL 主 UI,包含大量会话/权限/远程逻辑 |
cli/print.ts | Headless 入口 | 非交互式运行、SDK/结构化输出 |
QueryEngine.ts | 会话引擎 | 核心中的核心——主对话循环、工具调用、消息状态推进 |
query.ts | Query 管线 | 工具/消息流转、停机点与上下文处理 |
Tool.ts | 工具契约中心 | Tool Schema、权限上下文、执行上下文 |
commands.ts | 命令注册中心 | Slash 命令聚合与 Feature Flag 装配 |
tools.ts | 工具注册中心 | Tool 列表装配与环境裁剪(⚠️ 2026-10-17 补:列表顺序本身也会变,见下方注记) |
⚠️ 工具列表的顺序不只”排了没有”,还取决于”按什么规则排”,而规则本身会变。 v2.1.293 官方 changelog 逐字:
Changed the order of agent lists and of MCP servers announced to the model: names with non-ASCII characters now sort after ASCII names
这一条的直接后果是升级 v2.1.293 后的第一批会话,工具列表顺序与上一版不同。把它与第 5 章 5.4 节”排序后的工具列表在 System Prompt 中顺序稳定 → 提升 Prompt Caching 命中率”放在一起读,结论要补一格:缓存稳定性依赖的是”排序规则不变”,不只是”排序这件事存在”。一次排序规则变更就是一次全量缓存前缀失效事件——它不会报错,只会在账单上体现。⚠️ 此处”前缀失效”是本书推断(规则变 → 顺序变 → 前缀重写),官方 changelog 只描述了顺序变更,未提缓存影响,引用时须标为推断。
| entrypoints/init.ts | 启动初始化 | 配置、代理、遥测、清理、Scratchpad |
| setup.ts | 会话准备 | cwd、worktree、tmux、hooks、终端恢复 |
源码引用:以上枢纽文件列表来自源码分析知识库
01-源码结构与技术栈分析.md,基于导入深度、被引用次数和功能关键性综合评定。
其中 QueryEngine.ts 是整个系统的心脏——所有运行模式最终都进入这个会话引擎。它管理主对话循环:接收用户消息 → 构建提示 → 调用模型 → 解析响应 → 执行工具 → 循环。我们会在第 4 章详细拆解它。
2.4 横切基础设施:utils 的 18 万行
src/utils 是代码量最大的目录(564 个文件,180,487 行),但它不是一般意义上的”工具函数集合”。把它拆开看:
| 子模块 | 功能 |
|---|---|
utils/permissions/ | 权限规则解析、决策引擎、六层规则来源合并 |
utils/config/ | 配置文件读写(含 CLAUDE.md / AGENTS.md 的加载与模式选择,见下)、Feature Flag 管理 |
utils/git/ | Git 操作封装(status、diff、commit) |
utils/telemetry/ | 遥测与使用量统计 |
utils/session/ | 会话管理、历史记录 |
utils/ripgrep/ | ripgrep 封装(代码搜索引擎) |
utils/sandbox/ | 沙箱隔离机制(v2.1.271 起支持 per-command allowed_domains:Bash / PowerShell / Monitor 在 auto mode + 沙箱下,所需域名随该命令一并审查,只对该条命令开放,其余域名一律拒绝) |
utils/swarm/ | 多代理团队协作(teamHelpers.ts) |
utils/api.ts | API 调用辅助(工具模式转换、系统提示处理、缓存控制) |
utils/hooks.ts | 钩子系统(pre/post hook 执行、匹配逻辑、超时控制) |
这实际上是 Claude Code 的”操作系统层”——配置管理、权限引擎、Git 集成、遥测、沙箱,这些功能在其他项目中可能各自是独立的包。Anthropic 选择将它们全部放在 utils 下,这可能是快速迭代的产物,但也确保了模块间的低延迟调用。
项目指令文件的第二种可能:AGENTS.md(v2.1.277,2026-09-18)
utils/config/ 这一行里”配置文件”这个词,在 v2.1.277 之前是单数——项目指令只可能来自 CLAUDE.md。官方 changelog 原文:
Added AGENTS.md support: in a project with no CLAUDE.md, Claude Code reads AGENTS.md instead; change it under “Project instructions” in
/config
/config 的 Project instructions 有四种取值(官方文档 memory.md):
| 取值 | 行为 |
|---|---|
claude-md-or-agents-md(默认) | 读 CLAUDE.md;工作目录及其父目录都没有 CLAUDE.md / CLAUDE.local.md 时,改读 AGENTS.md |
claude-md-and-agents-md | 两者都读,每个目录先 CLAUDE.md 后 AGENTS.md;已加载过的 AGENTS.md 不重复读 |
claude-md | 只读 CLAUDE.md |
managed-only | 启动时只加载企业托管 CLAUDE.md 与自动记忆;项目/本地/用户级 CLAUDE.md、.claude/rules/ 与所有 AGENTS.md 全部排除(子目录的 CLAUDE.md 与 .claude/rules/ 仍在读取该目录文件时加载) |
⚠️ 三条必须一起记住的边界(官方文档,不是社区猜测):
CLAUDE.local.md也算”存在 CLAUDE.md”。它会与CLAUDE.md、.claude/CLAUDE.md一起参与判定,一旦出现在工作目录或任一父目录,就静默抑制整条路径上的 AGENTS.md 回退。官方给的修法是改用claude-md-and-agents-md。不计入判定的只有~/.claude/CLAUDE.md、企业托管CLAUDE.md与.claude/rules/。- 通过 Project instructions 读入的
AGENTS.md不触发InstructionsLoadedhook——但被CLAUDE.md用@AGENTS.mdimport 或 symlink 引入时会正常触发。同理,--add-dir加进来的目录(在CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD开启时)其AGENTS.md不加载。 - 后端覆盖是分阶段的:v2.1.277 引入时,部分会话(例如 Amazon Bedrock、以及关闭遥测的会话)只读
CLAUDE.md;v2.1.281 起已扩展到 Amazon Bedrock、Google Vertex AI、Microsoft Foundry、LLM 网关与关闭遥测的会话。另有两条”看起来坏了其实不是”的场景:低于 v2.1.277;在/plugin里禁用了内置agents-md插件;以及从 v2.1.276 或更早升级后的第一次会话(下一次会话起生效)。
📌 架构上最值得注意的一点:AGENTS.md 支持是以内建插件 agents-md@builtin 的形式实现的——可以在 /plugin 里禁用,也可以在 ~/.claude/settings.json 的 pluginConfigs 里配置:
{
"pluginConfigs": {
"agents-md@builtin": { "options": { "instructionFiles": "claude-md-and-agents-md" } }
}
}
这与 2.2 节入口层那条 --plugin-dir 是同一件事的两面:连”读哪个指令文件”都被下沉成了插件层的一个可配置、可禁用项。对逆向研究而言,这意味着”配置层”的边界比源码目录结构暗示的更宽。
治理因果链:AGENTS.md 由 OpenAI 于 2025-08 发布,2025-12-09 捐给 Linux Foundation,现由 Agentic AI Foundation(AAIF) 托管——它是 AAIF 三个创始捐赠项目之一(另两个是 Anthropic 的 MCP 与 Block 的 goose),官方称已被 60,000+ 开源项目与 Agent 框架采用。这条链解释了本章此前无法解释的一件事:Anthropic 为什么会去支持一个竞争对手发起的指令文件格式——当这个格式已被 6 万个项目采用、且由中立基金会持有时,不支持它的成本高于支持它。(第 1 章 1.4 节从”为什么值得逆向研究”的角度写了同一条链的另一个侧面。)
2.5 技术栈选择
| 技术 | 用途 | 评价 |
|---|---|---|
| TypeScript + TSX | 主体语言(1,332 + 552 个文件) | 全面强类型,贯穿全栈 |
| React + Ink | 终端 UI | 组件化终端渲染,支持状态管理和虚拟 DOM |
| Zod | 运行时数据验证 | 50+ Schema 定义,确保工具输入/配置的类型安全 |
| Bun | 构建打包 | 快速打包(也是导致泄露的原因——默认生成 source map) |
| OAuth 2.0 + PKCE | 认证 | 安全的第三方登录流程 |
| Jest | 测试 | 单元测试和集成测试 |
源码引用:技术栈总结来自源码分析知识库”项目概述 - 技术栈总览”。
打包体积演进:2026-08 之后的四个数据点
上表 “Bun / 构建打包” 一行的评价停留在 2026-04 的泄露事件(默认生成 source map)。2026-08 之后,打包工程是持续投入的方向,且给出了可引用的具体数字:
| 版本 | 日期 | 改动 | 官方数据 |
|---|---|---|---|
| v2.1.243 | 2026-08-25 | 原生安装与自动更新下载改为 zstd 压缩 | Linux x64 约 75 MB 而非 340 MB |
| v2.1.243 | 2026-08-25 | 更紧凑地存储捆绑技能与提示文本 | 原生二进制小约 2 MB |
| v2.1.243 | 2026-08-25 | 代码按需加载,不再常驻整个 bundle | 每会话内存少约 40–70 MB |
| v2.1.251 | 2026-08-28 | 移除 1c、gml、isbl、mathematica、maxima、sqf 六种极少使用语言的语法高亮 | 二进制小 2.5 MB |
| v2.1.251 | 2026-08-28 | 安装体积优化 | 原生二进制小约 5 MB |
同一版本的 v2.1.243 还改了启动路径:沙箱与 MCP 的拉起不再阻塞第一帧,裸启动跳过子命令注册,workflow 发现、设置与 trust-store 的开销降低。
这些数据合起来指向一个结论:捆绑内容——技能、提示词、语法高亮、图像模块——的体积是原生安装的一等公民成本。这与第 6 章讨论的上下文体积是同一个问题的两面:一边压缩的是运行时塞进模型上下文的 token,另一边压缩的是分发到本地磁盘与内存的字节。
2026-09 的两个版本把”两面”补成了三面——体积不只是分发时和推理时要压缩,单次工具产出也有自己的配额:
| 体积的哪一面 | 压缩的是什么 | 证据 |
|---|---|---|
| 分发体积 | 磁盘与内存字节 | v2.1.243 zstd(Linux x64 约 75 MB 而非 340 MB)、每会话省 40–70 MB;v2.1.251 二进制小 5 MB / 2.5 MB |
| 上下文体积 | 塞进模型上下文的 token | 第 6 章展开;MCP 工具描述与 server 指令:v2.1.295 起按加载路径分成四条轨道(见下表),不再是单一的 2,048 |
| 产出体积 | 单次工具结果落盘 | v2.1.265 起上限 1 GB,超出即截断,且会话内预览会明示截断 |
三面各自的”超了会怎样”也不一样:分发体积超标是安装慢、内存高;上下文体积超标是模型看不见(或被裁剪);产出体积超标是执行成功但结果不完整——只有第三种会明确告诉你。
MCP 描述配额的四条轨道(2026-10-17):
| 轨道 | 上限 | 版本 | 官方 changelog 原文要点 |
|---|---|---|---|
会话级总开关 CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH | 可调(原默认 2,048) | v2.1.280 | 对该会话所有 MCP server 统一生效 |
| 经 tool search 加载的工具描述 | 16,384(原 2,048) | v2.1.295 | ”descriptions the model loads through tool search” |
| sent up front 的工具描述 + server instructions | 4,096(原 2,048) | v2.1.296 | ”the default limit on MCP tool descriptions sent up front and on MCP server instructions” |
Anthropic 第一方 server(api.anthropic.com) | 不截断 | v2.1.299 | ”to reach Claude in full instead of being cut at 4,096 characters” |
方法论一句:同一个”上限”在 11 天内被拆成四条轨道,说明“上下文体积”不是一个标量,而是按加载路径分叉的一组量。这同时修正了一个容易犯的错——把 v2.1.295 记成”MCP 描述上限默认值改为 16,384”是不完整的,16,384 只适用于 tool search 那一条轨道;up-front 轨道的默认值在次日(v2.1.296)变成了 4,096,两者相差 4 倍。
四条轨道的”超了会怎样”也不同:up-front 超限是描述被裁短(模型看到的信息变少);tool search 超限是搜到的工具描述不完整(搜索命中但内容残缺);第一方不截断这条轨道压根没有上限。只有第三种会明确告诉你发生了什么——“有上限”这件事本身也是一条轨道的属性。
几个值得注意的技术选择:
-
React 用于终端 UI:这是一个大胆的选择。大多数 CLI 工具使用
readline或blessed,Claude Code 选择了 React + Ink,获得了组件复用和声明式渲染的好处,但也引入了额外的复杂度(96 个文件的 Ink 定制代码就是证据)。 -
Zod 做运行时验证:TypeScript 的类型系统只在编译时有效。Zod 补上了运行时验证——当模型返回的工具调用参数不符合 Schema 时,可以在运行时捕获并给出有意义的错误信息。
-
不使用任何 AI 框架:没有 LangChain、没有 LlamaIndex、没有 AutoGen。Claude Code 从零构建了自己的 Agent Loop、工具系统、权限系统。这是有意为之——生产级系统需要对每个环节的精确控制,通用框架的抽象层反而是障碍。这种”零框架自研”的选择在 2026 年被社区广泛讨论为 Harness Engineering 的典型实践:模型周围的一切基础设施(工具编排、权限、上下文管理、错误恢复)构成 Agent 的”Harness”,对其每一层的精确控制比框架便利性更重要。
2.6 模块化设计原则
在逐条列举设计原则之前,值得先退一步审视更高层的问题:这些原则服务于什么样的设计目标?
arXiv 论文 2604.14228(Dive into Claude Code: The Design Space of Today’s and Future AI Agent Systems,VILA-Lab,2026-04)对 Claude Code 的设计空间做了系统化分析,提出五大设计动机——即架构层面”为什么这么做”的价值观根基:
| 动机 | 含义 | 对应的工程选择 |
|---|---|---|
| Human Decision Authority | 人类保留最终决策权 | 六层权限规则 + AI 分类器(第 7 章) |
| Safety & Security | 安全是不可协商的前提 | Fail-Closed 默认值、Docker 沙箱、破坏性操作拦截、供应链加固、输入净化(后两项为 2026-09 新增) |
| Reliable Execution | 在长时间运行中保持可靠 | 五层上下文压缩、错误分类与重试、熔断器模式 |
| Capability Amplification | 持续扩展能力边界 | MCP 协议、插件系统、技能系统、子代理委派 |
| Contextual Adaptability | 适应不同用户和场景 | Feature Flags、动态 System Prompt、配置层级(含项目指令文件 CLAUDE.md / AGENTS.md 的多格式回退与模式选择) |
论文将 Claude Code 与 OpenClaw 做了对比分析,展示同一组设计动机在不同部署场景(本地终端 vs 云端容器)下产生的不同实现选择。这些动机不是源码里写着的——它们是从架构模式中反推出的驱动力,帮助理解下面 6 条工程原则的”为什么”。
Safety & Security 动机的 2026-09 ~ 10 六条新证据
泄露代码是 2026-03-31 的快照,而”安全是不可协商的前提”这条动机在此后持续产生新的工程选择。2026-09 一个月内就有四条,2026-10 又添两条,且形态各异——不是同一招用了六遍:
| 版本 | 形态 | 官方 changelog 要点 |
|---|---|---|
| v2.1.275 | 供应链 | npm 来源的插件改用 npm pack --ignore-scripts 获取并做完整性校验,包的 install scripts 不再执行 |
| v2.1.277 | 输入净化(可见化) | 提示中的不可见 Unicode 格式化与标签字符被移除,且清理后的提示会在发送前展示给你复核 |
| v2.1.271 | 最小权限粒度下沉 | per-command allowed_domains:Bash / PowerShell / Monitor 在 auto mode + 沙箱下,所需域名随该命令一并审查,只对该条命令开放,其余域名一律拒绝 |
| v2.1.271 | 指令作用域裁剪(per-agent) | omitClaudeMd 加入 agent frontmatter 与 --agents JSON,让自定义与插件子代理不加载用户/项目/本地 CLAUDE.md(托管策略文件仍加载) |
| v2.1.286 | 供应链(来源形态收窄) | 插件安装拒绝 git 仓库或文件夹形式的 npm 源,插件依赖只能从 registry 包安装(官方 changelog 逐字) |
| v2.1.289 | 工具描述完整性 | 修掉”用户安装的插件可以改写组织托管 MCP server 的登录工具描述”(官方 changelog 逐字) |
第二条值得单独说一句:它和本章前面 buildTool 那类 fail-closed 处理不是同一招。fail-closed 是”拿不准就拒绝,你不必然知道发生了什么”;这条是“我改了你的输入,并且改了什么摆给你看”——同一个动机长出了两种相反的表达方式。安全工程里这两招都合理,但把它们当成一回事会误判 Anthropic 的设计取向。
第三条把最小权限的粒度从”会话级 / 工具级”下沉到了单条命令:域名不是你配一份白名单全局生效,而是随那条命令一起被审查、只对那条命令开放。
第四条与 2.4 节的 AGENTS.md 是一对,构成”指令作用域”的两个控制层次:
- 全局层(
/config→ Project instructions):决定这个仓库读不读AGENTS.md、以及和CLAUDE.md怎么组合 - 单代理层(agent frontmatter 的
omitClaudeMd):决定某个具体子代理读不读用户/项目/本地CLAUDE.md
前者管”有哪些指令文件进入会话”,后者管”这些文件对谁生效”。把这两层分开,是”子代理应该有自己独立作用域”这个判断落到配置层的证据——第 8 章讲多 Agent 协作时会再遇到它。
第五条与第一条要放在一起读:它们是同一族,但收紧点前移了。 v2.1.275 是”拿到了不执行脚本”(npm pack --ignore-scripts + 完整性校验),v2.1.286 是”根本不允许从 git 仓库或文件夹拿”(依赖只能来自 registry 包)。同一类威胁的两道闸门从执行期退到了获取期——供应链防线的位置在往前移,这与本书第 5 章讲的”防线越靠前,需要信任的东西越少”是同一条。
第六条是一条全新形态,也是六条里唯一一条”官方修复实例”。 前四条防的是”工具做了坏事”或”指令被污染”,这一条防的是可信方之间的描述互相覆盖:用户侧安装的插件顶替了组织托管 MCP server 的登录工具描述,等于托管信任等级被用户侧内容覆盖。这正是第 5 章 CSA「Poisoned Foundations」所述 tool poisoning 的形态——区别在于这一条有官方修复,比研究报告更硬:研究报告只能证明攻击可行,官方 changelog 的一条 Fixed 证明它真的发生过、且已被修。
从源码中可以提炼出 Claude Code 遵循的几条设计原则:
| 原则 | 实现方式 |
|---|---|
| 分层架构 | 入口层 → 命令层 → 工具层 → 服务层 → 组件层,依赖方向单向向下 |
| 模块化 | 35 个顶层目录,每个目录职责单一 |
| 按需加载 | Feature Flags 实现动态加载,支持 Tree Shaking 减少启动时间 |
| 依赖注入 | React Context 实现组件间的依赖注入 |
| 类型安全 | TypeScript 编译时 + Zod 运行时,双层类型保障 |
| Fail-Closed | 安全相关的默认值选择更保守的选项。2026-09 的两条加固给这条原则补了两个新样本:npm 插件改用 npm pack --ignore-scripts 并做完整性校验(v2.1.275)与per-command allowed_domains 默认只开该命令所需的域名(v2.1.271) |
源码引用:设计原则总结自源码分析知识库”工程结构 - 模块化设计原则”,引用 main.tsx、commands.ts、Tool.ts。
2.7 与其他 AI 编程工具的架构对比
在跨产品对比之前,先处理一个容易让读者困惑的问题:同一份源码存在不止一种拆解方式。
本章采用的”五层架构”(入口 / 命令 / 工具 / 服务 / 组件)按依赖方向划分,层间单向向下。社区还流传着另一种划分——把源码拆成 25 个子系统、再归纳为 7 个功能域(入口层、核心引擎、模型交互层、工具系统、安全与权限、输出与展示、扩展系统),按的是功能关注点。两者的关键差异在于后者的”模型交互”与”扩展系统”被提升为独立域,而在五层模型里它们分别被折叠进服务层和工具层。
两种划分不冲突,只是切法不同:前者适合追踪一次调用从上到下的穿透路径,后者适合按职责定位代码。读者在对照社区资料时会同时遇到这两种,这里先做预告。
📌 待核实:25 子系统 / 7 功能域的具体清单来自中文技术社区的拆解文章,非官方来源,也未经过同行评审。上述差异描述基于对该划分的概要转述,引用时建议回到原始拆解资料核对子系统清单。
为了更好地理解 Claude Code 的架构选择,把它和同类产品做个简要对比:
| 维度 | Claude Code | Cursor | Aider | OpenHands | Claw Code |
|---|---|---|---|---|---|
| 运行环境 | 终端(React + Ink) | VS Code 扩展 | 终端(纯 Python) | 浏览器 + Docker | 终端(Rust 92.9% + Python 7.1%) |
| 代码规模 | ~51 万行 TS | 非公开 | ~3 万行 Python | ~10 万行 Python | Rust 核心 + Python 测试框架 |
| Agent Loop | 自研 QueryEngine | 非公开 | LiteLLM 调度 | 自研 EventStream | 自研(Rust 核心,Claude Code 架构复刻) |
| 工具系统 | 42+ 内置 + MCP | 非公开 | ~10 个内置 | ~20 个 | 30+ 内置 + MCP |
| 多代理 | 6 种内置代理 | 非公开 | 无 | 有 | 有(多代理编排) |
| 权限模型 | 六层规则 + AI 分类器 | IDE 权限 | 用户确认 | Docker 沙箱 | 用户确认 + 沙箱 |
| 框架依赖 | 零框架,全部自研 | 非公开 | LiteLLM | 部分自研 | 零框架(Clean-room 重写) |
Claw Code(claw-code.codes,150k+ GitHub stars)是 Claude Code 架构的 Rust clean-room 复刻。它已完成从”Python 为主 + Rust 为辅”到”Rust 92.9% + Python 7.1%“的架构迁移——核心 CLI/运行时/插件系统/内存管理全部由 Rust 实现,构建产出单文件二进制(213MB ELF),性能远超原 TypeScript 实现。它的存在本身是对 Claude Code 架构可移植性和影响力的有力证明——核心设计模式(Agent Loop + 工具系统 + MCP 集成)被证明可以跨语言栈复现。
社区学习资源:shareAI-lab/learn-claude-code(原 analysis_claude_code)是一个 65.7k stars 的系统课程,提供中/英/日三语 20 节课程,从 agent loop 讲到 MCP 扩展,是目前对中文开发者最友好的 Claude Code 深入学习资料。