昨天Claude Code的源码泄露,在技术圈引起了广泛讨论。各种分析文章层出不穷,有的盘点隐藏功能,有的统计工具数量,还有的深入剖析了核心模块query.ts。我自己也花了一晚上翻阅代码,收获颇丰。
但看得多了会发现一个问题:信息虽然很多,但如果想形成一篇连贯的文章,很容易陷入细节,主线反而模糊。
所以这篇文章,我想收一收视角,只沿着一条主线深入:
Claude Code 这套本地 Agent Runtime,从启动到上下文装配、主循环,再到权限管道,究竟是如何一层层构建起来的。
如果说之前的文章是从外部看轮廓,那么这次有了源码,我们终于可以把观察落到具体的文件上了。我的分析未必完全准确,但至少是基于代码的,比二手信息更扎实。
核心要点(TL;DR)
- 本次泄露的核心是 Claude Code 的工程实现层,而非模型权重本身。
- 从架构角度看,最值得关注的主线是:启动、上下文装配、主循环、工具契约、编辑约束、权限管道、长任务续航这几层的连接方式。
src/main.tsx 表明其从一开始就是作为长期交互系统来优化的,而非一次性 CLI。
src/constants/prompts.ts 和 src/utils/queryContext.ts 显示,Prompt 在这里更像一个装配层,而非静态文本。
src/QueryEngine.ts、src/utils/processUserInput/processUserInput.ts、src/query.ts 共同构成了运行主链路。
src/Tool.ts、src/tools.ts、src/tools/FileEditTool/* 最能体现 Anthropic 如何将“先读再改”、“默认保守”原则机制化。
src/utils/permissions/permissions.ts 是一个权限决策管道,而不仅仅是一个确认弹窗。
- 上下文压缩分为三级,长期记忆与会话状态也做了分离。
- 然而,高权限也意味着更大的攻击面:hooks、MCP、Skill 等确定性执行入口,一旦被项目级配置投毒,可能导致静默的远程代码执行。
明确边界
翻阅代码后,一个比较一致的感受是:这次泄露的核心价值在于 Claude Code 的产品实现,而不在于 Claude 模型本身的推理机制。
外界现在能看到的是“这套本地 Agent 系统如何运作”,而非模型内部的奥秘。这个区分很重要,否则分析很容易滑向两个极端:要么过度解读模型能力,要么沦为简单的功能罗列。
从工程角度看,更有价值的恰恰是中间层:它是如何收集上下文、组织主循环、约束工具行为、处理长任务,并在本地高权限环境下尽可能前置化风险的。我们的主线就落在这里。
先看主链路,而非数功能
要建立整体认知,比起数工具数量,更值得先理清一条核心执行链路:
main.tsx 将启动阶段能并行的事情提前处理。
QueryEngine.submitMessage() 接收一轮新输入,准备会话级状态。
fetchSystemPromptParts() 拉取 system prompt、user context、system context。
processUserInput() 将原始输入整理成真正送入模型的一轮消息。
query.ts 驱动主循环,处理工具调用、预算控制、压缩、继续执行等状态迁移。
- 工具系统执行读、写、搜索、命令、MCP 等动作。
- 权限系统在工具层、规则层和模式层多次参与裁决。
- 上下文过长时触发压缩(compact);长期任务状态则交由 memory 体系维持。
一旦理清这条链路,就能理解为什么它的代码量看起来更“重”,以及为什么它不只是一个终端聊天工具。

main.tsx 的信号:按 Runtime 设计
很多分析都提到了 src/main.tsx 开头的并行预取。这个点很关键,但意义不止于“并发优化很聪明”,它更揭示了一种工程取向。
在文件顶部,系统会提前触发 profile checkpoint、MDM 读取、keychain 预取等操作,并尽量与后续的 import 过程重叠。注释里甚至直接说明了这样做与启动耗时的关系。
这说明它从一开始就在解决一个非常实际的问题:
如果这个工具需要被频繁打开,那么启动链路就不是边角问题,而是运行时体验的核心部分。
这一点与我们之前在讨论 Agent Harness 时的感受一致。启动时间、状态恢复、权限切换、上下文预热,都属于同一层“运行时体验”。main.tsx 或许不是最复杂的文件,但它最早表明了 Anthropic 将 Claude Code 定位为何种产品。
Prompt 是装配层,而非“神奇咒语”
不少文章提到 Claude Code 的系统 Prompt 很长。观察没错,但如果仅止于此,就错过了一层深意。
从 src/constants/prompts.ts 和 src/utils/queryContext.ts 来看,更贴近工程实际的描述是:
Claude Code 将 Prompt 做成了一层可装配、可分段、可缓存的运行时上下文。
有几个关键细节:
getSystemPrompt() 并非返回一个大字符串,而是先按 section 组织,再拼接。
SYSTEM_PROMPT_DYNAMIC_BOUNDARY 明确划分了静态前缀和动态部分。
fetchSystemPromptParts() 会并行准备 defaultSystemPrompt、userContext、systemContext 这三块用于 API 缓存键的前缀材料。
这意味着它关心的不再是“提示词是否优美”,而是:
- 哪些内容可以稳定复用?
- 哪些内容必须每轮重新感知?
- 哪些上下文属于会话状态?
- 哪些上下文属于系统环境?
这和我们之前探讨上下文治理时的思路一致:问题往往不在于 token 不够,而在于组织方式不够系统化。
另一个细节是:每个工具目录下还有独立的 prompt.ts。例如 BashTool/prompt.ts 中直接写明了 Git 安全协议——“NEVER run destructive git commands unless the user explicitly requests”。这些是写给 AI 看的行为准则,而非用户手册。工具的顺序也是固定的,目的是保持 Prompt Cache 的字节级前缀稳定。
也就是说,Claude Code 把 Prompt 管理做成了一件类似编译器优化的事。静态部分是“编译后的二进制”走缓存,动态部分是“运行时参数”每次装配。从这个角度看,它的 Prompt 体系并不神秘,只是更早地将“上下文装配”当成了一层正式的工程对象。

运行主链路的承重者:QueryEngine 与 query.ts
如果说 main.tsx 负责拉起系统,那么决定 Claude Code 气质的,是后续的运行主链路。建议将三个文件放在一起看:
src/QueryEngine.ts
src/utils/processUserInput/processUserInput.ts
src/query.ts
QueryEngine.submitMessage() 的作用远不止“把用户输入发给模型”。从代码看,它会准备 cwd、tools、commands、mcpClients、thinkingConfig、budget、session state,然后调用 fetchSystemPromptParts();接着结合 memory prompt、custom prompt 等组装出真正的 system prompt;最后构建 processUserInputContext,将 canUseTool、readFileState、requestPrompt、session storage 等运行时能力一并传入。
processUserInput() 也不只是简单的字符串预处理。它会先处理斜杠命令(slash command)、附件、图片、IDE 选区、粘贴内容,然后在继续之前执行 executeUserPromptSubmitHooks(),必要时拦截整轮执行或附加额外上下文。
到了 query.ts,系统才进入最核心的 Agent Loop。消息准备、工具调用、结果回填、token 预算、stop hooks、continue 状态、compact 判断,都在此汇集。
query.ts 的“庞大”更像是一种刻意保留的“胖核心”。因为 Claude Code 这类系统最难管理的不是单个模块,而是跨轮次状态:
- 消息何时进入上下文?
- 工具结果何时被裁剪?
- 哪些中间状态需要持久化?
- 何时该停止,何时该继续?
- 何时该压缩历史,何时该保留原始链路?
如果将这些逻辑拆得过散,表面模块是“瘦”了,但复杂度可能只是转移了位置。因此,将 query.ts 放回 Agent Runtime 的语境中,它为何如此设计就比较容易理解了。
还有一个细节:源码中能看到 USER_TYPE === 'ant' 的分支。这意味着 Anthropic 内部员工使用的版本与外部版本在提示词策略上存在差异。内部版本有更激进的输出策略、更详细的代码风格指引,以及一些尚在 A/B 测试的实验功能。这说明 Anthropic 自己就是 Claude Code 的最大用户,他们用自己的产品来开发产品。
工具系统:先声明边界,再开放调用
有些分析会罗列 Claude Code 支持多少工具和命令。这些信息有用,但更值得关注的是:这些工具在进入系统之前,其边界是否已被明确定义。
这里最值得看的是 src/Tool.ts 和 src/tools.ts。
在 Tool.ts 中,一个工具不能只是“能运行就行”,还必须回答一组具体问题:
isReadOnly
isConcurrencySafe
isDestructive
needsUserInteraction
checkPermissions
更重要的是,buildTool() 背后的默认值是保守的。isConcurrencySafe 默认为 false,isReadOnly 也默认为 false。
这并非小事。它表明系统默认不信任一个“没有写明安全属性”的工具。只要开发者漏配了关键声明,系统就会先将其视为可能进行写入操作、并发不安全的类型。
许多产品的节奏是“先把工具接上,规则以后慢慢补”。Claude Code 反其道而行:先要求声明清楚安全属性,再暴露给模型。起步或许慢一点,但后期在权限和并发上欠的技术债会少很多。
如果要从整个仓库中挑选一组最能体现 Claude Code 工程风格的文件,那很可能是:
src/tools/FileEditTool/prompt.ts
src/tools/FileEditTool/FileEditTool.ts
一方面,prompt.ts 中明确规定:在编辑文件前,至少要先使用 Read 工具读取一次。关键在于,这句话与工具本身的校验逻辑是配套的。如果模型未读文件就直接调用编辑,系统会报错拦截。它在系统层面将“先读再改”做成了硬约束,而不仅仅是提醒。
另一方面,FileEditTool.ts 中的保护措施非常朴素,但很工程化:
- 先进行路径规范化,避免
~、相对路径等写法影响规则匹配。
- 在真正修改前进行权限校验。
- 对禁止访问的目录(denied directory)进行明确拦截。
- 对大文件进行保护。
- 对文件不存在、旧字符串不唯一、空文件等情况分开处理。
单看任何一条都不惊艳,但组合起来,你能看到一种一致的设计取向:
不要过度依赖模型的“自觉”,能在机制上提前约束的地方,尽量机制化。
源码中反复体现的正是这种倾向:Claude Code 一直在将易错点推向系统约束,依靠的是工程纪律,而非某个神奇技巧。

权限系统:一条决策管道,而非简单弹窗
安全是这次讨论中容易失焦的话题。一旦涉及本地文件、命令执行、MCP、配置入口,分析很容易停留在“这很危险”的结论上。
风险当然存在,但如果我们只停留在风险描述,就错过了一层更深的价值。从 src/utils/permissions/permissions.ts 来看,Claude Code 更值得注意的地方在于,它将权限决策拆解成了一条可治理的链路。
沿着 hasPermissionsToUseToolInner() 往下看,大致顺序如下:
- 判断是否命中 deny tool 规则。
- 判断是否命中 ask tool 规则。
- 进入工具自身的
checkPermissions 方法。
- 处理内容级 ask rule 和 safety check。
- 检查当前模式(mode),例如
bypassPermissions。
- 检查是否为 always allow 情况。
- 最后将
passthrough 收敛为 ask。
同一文件中还能看到 classifier、PermissionRequest hooks、MCP 规则匹配、denial tracking 等补充机制。
所以,用一句更贴近工程的话总结:
Claude Code 的权限系统,更像一条可以持续治理、持续插入规则的决策管道,远不止一个弹窗确认。
这也是其实现显得比较“重”的原因。一旦产品需要在本地环境中持续运行,权限就不可能只是一层“最后再补”的界面逻辑。

长任务续航:回到 Compact 与 Memory
还有一点值得与我们之前讨论的上下文治理结合来看:Claude Code 并未将所有希望都寄托在“大窗口模型”上,而是单独设计了 compact 和 memory 两套机制。
src/services/compact/prompt.ts 显示,compact 阶段会使用专门的提示词来约束总结方式,并且明确禁止调用工具,要求尽量保留用户请求、相关文件、关键代码、错误、待办事项、当前工作状态等结构化信息。
src/services/extractMemories/prompts.ts 和 src/services/SessionMemory/prompts.ts 则将“长期记忆提取”与“当前会话状态维护”分开了。后者尤为直接,直接维护 Current State、Task specification、Files and Functions、Errors & Corrections、Worklog 等区块。
背后的道理很朴素。长任务真正的难点在于“状态续不上”,而不只是“字数太多”。历史该如何压缩是一个问题,当前任务状态该如何维持是另一个问题。
实际上,Claude Code 的压缩不止一层。从代码中可以看到它做了三级压缩:
- microcompact:仅清理旧的工具调用结果,保留对话主线。
- autocompact:当 token 消耗接近上下文窗口的 87% 时自动触发。
- 完全压缩:让 AI 对整个对话生成摘要来替换历史。
值得注意的是,在完全压缩阶段有一条非常严格的前置指令——“CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.”——因为如果总结过程中 AI 又去调用工具,就会产生更多 token 消耗,适得其反。
Claude Code 的做法未必最轻量,但它至少将这两个问题拆开处理了,并且在压缩策略上分了梯度,力求在每个阶段都做最小侵入的处理。从工程完整性上看,这一点是比较扎实的。对于这类 Agent 系统的 源码分析,理解其状态管理机制至关重要。

高权限意味着更大的攻击面
然而,如果只谈工程设计的扎实,还欠缺一层。昨天的讨论中,有一类风险值得单独提出来:配置投毒。
有安全研究者和 UP 主实测演示了一个场景:只要克隆一个包含恶意 .claude/settings.json 的仓库,运行 claude 命令后,hooks 字段里定义的脚本会静默执行——不弹窗、不确认,可能导致摄像头被调起、密码被窃取。
问题的根源不难理解。Claude Code 的 hooks 机制设计初衷是实现自动化,例如编辑文件后自动运行 lint,提交前自动进行类型检查。但 hooks 的执行不经过模型判断,也不走权限弹窗——它被设计为“确定性脚本,100% 执行”。
问题在于,当这个确定性入口默认信任项目级配置文件时,攻击者就可以将恶意命令藏在一个看起来正常的开源项目里。
投毒路径不止 hooks 一条。.mcp.json 可以配置恶意的 MCP 服务器,skill 文件的 frontmatter 也可以定义 hooks。这三条路径的共同点是:都是 Claude Code 默认信任、不做二次确认的配置入口。
Check Point Research 在相关报告中的一句话很贴切:
曾经作为被动数据的配置文件,如今成了主动执行路径的控制器。
这与我们之前的观点一致:当 Agent 开始真正动手操作时,安全边界是停留在产品文案中,还是已内化为工程现实,成了一个必须正面回答的问题。Claude Code 的权限管道在工具层做了不少工作,但在配置信任边界上,这次暴露的攻击面说明仍有改进空间。对所有开发 Agent 的团队而言,这个教训同样适用:
Hooks、MCP、Skill 这些“扩展入口”越强大,被投毒时的危害也越大。确定性执行是优势,但无条件的确定性信任可能是风险。
值得借鉴的朴素工程实践
纵观各方的分析,整体规模感、风险边界提醒、核心模块拆解都已具备。大家越来越清楚的一点是:这次泄露的核心价值在于工程实现。
回归代码本身,我想说的核心观点是:
Claude Code 更值得借鉴之处,在于它将许多原本只写在最佳实践文档里的原则,一步步推进成了代码中的系统约束。
例如:
- Prompt 的分层与装配。
- 工具使用前的边界声明。
- 文件编辑的“先读后改”机制。
- 权限系统的决策链设计。
- 长任务中“历史压缩”与“状态续写”的分离处理。
这些做法听起来并不新颖。但一旦被真正写入系统,产品的体验和稳定性通常就会有显著提升。因此,没必要将 Claude Code 神化为“操作系统”或“黑科技”。更朴素的解读可能是:
它只是比较认真地将一套本地 Agent Runtime 所需补齐的工程层,一层层地构建了起来。
而 AI 编程产品未来真正拉开差距的地方,或许也越来越体现在这些 架构设计 与工程实践的深度上。