
下午五点半,看到微信群里有人转发疑似 Anthropic的Claude Code CLI 源码泄露的信息,似乎是因 npm source map 配置失误导致的。
刚看到仓库https://github.com/instructkr/claude-code(不少人最初怀疑代码是否是真的来自Anthropic,但github很快就将仓库屏蔽了,因为违反DMCA,最初的代码已经没了,请到https://wwbfy.lanzouq.com/ioOiB3m0k3rg下载),git star才371,这会7.8k,估计你看到文章的时候要过11k了。

朋友圈已经有朋友分享其架构设计了(如下图)。

我想从另外一个角度,不从功能模块,而是其设计决策 及 7点启发。
一、最大的反共识:Agent 框架的核心不是"智能",而是"刹车"
有一种创业者默认共识的叙事:Agent 的核心竞争力是更聪明的推理、更好的规划、更强的 tool calling 能力。
Claude Code 用 2600 行代码告诉我,这是错的。
`src/tools/BashTool/bashPermissions.ts` 这个单文件有 **2600 行**,比整个 QueryEngine(Agent 主循环)还长一倍。它做的事情只有一件:**判断一条 bash 命令能不能执行**。
这不是简单的黑名单。它用 tree-sitter 解析 bash 命令的 AST,检测嵌套命令注入:
# 看起来无害的 echo,实际执行了 rm
echo "$(rm -rf /)"
# 正则匹配会漏掉,但 AST 解析能发现 CommandSubstitution 节点它剥离环境变量前缀来防止绕过:
# 用户的 deny 规则禁止了 rm,但加上环境变量就能绕过简单匹配
FOO=bar rm -rf /
# Claude Code 会先剥离所有 env vars,再匹配 deny 规则它甚至理解 git 的 `-c` flag 可以触发 RCE:
# git -c core.pager="sh -c 'malicious'" log
# 单独检测 attached 形式 (-ccore.pager=...) 和 space-separated 形式 (-c core.pager=...)**这给AI创业者的启发是什么?**
很多人在做 AI Infra 时,本能地把 80% 精力放在"让 Agent 更能干"——更好的 prompt、更强的模型、更多的工具。
但 Claude Code 的工程投入告诉我们:**在生产环境中,"让 Agent 不闯祸"的工程量,可能是"让 Agent 更聪明"的 3 倍。**
Anthropic 的四层安全架构值得每个做 Agent 产品的团队参考:
Layer 1: 权限模式 (default/auto/plan/bypass)
↓
Layer 2: 规则引擎 (deny > ask > allow,deny 优先级最高)
↓
Layer 3: 工具级安全检查 (AST 解析、路径约束、注入检测)
↓
Layer 4: 沙箱隔离 (文件系统 + 网络限制)安全规则永远优先于便利性规则
deny > ask > allow这不是拍脑袋定的,而是src/utils/permissions/permissions.ts 1500 行代码严格实现的。
二、最优美的设计:一条分界线省下 90% 的 token 费用
在 `src/constants/prompts.ts` 第 114 行,有一个看起来平平无奇的常量:
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
'__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'这条分界线将 system prompt 切成两段:
在 API 调用时,静态段使用 `scope: 'global'` 的 prompt caching:
// src/utils/api.ts
// 静态内容:全局缓存,所有用户共享
cache_control: { type: 'ephemeral', scope: 'global' }
// 动态内容:不缓存这意味着 Claude Code 数百万用户共享同一份缓存的 system prompt 前缀。考虑到 system prompt 可能有数千 token,cache read 的价格是正常价格的 10%,**这一条分界线就可以节省 90% 的 system prompt 计费**。
但更精妙的是为了维护这个缓存而做的工程:
`src/services/api/promptCacheBreakDetection.ts` 实现了完整的 **缓存命中率监控系统**。它在每次 API 调用前计算 ~15 个维度的 hash(包括每个工具的 schema hash),然后对比响应中的 `cache_read_input_tokens`。如果缓存读取 token 数下降超过 2000,它会精确诊断是哪个工具的 schema 变了(占缓存失效的 77%),还是 TTL 过期(gap > 1 小时),还是其他原因。
**这给AI创业者的启发是什么?**
做 AI Infra 的人都知道 prompt caching 很重要,但我之前从没想过要把它做到"监控每次缓存未命中的 root cause"这个粒度。**在 LLM 应用中,prompt 不仅是"给模型的输入",更是一个需要精心管理的缓存资产。** 就像传统后端对 Redis 缓存命中率的监控一样,LLM 应用需要对 prompt cache 命中率有同等级别的可观测性。
三、最反直觉的选择:用 require() 而非 import,用文件而非数据库
Claude Code 是一个纯 TypeScript/ESM 项目,运行在 Bun 上。但它在 **60+ 个地方**使用了 CommonJS 的 `require()`:
// src/tools/AgentTool/builtInAgents.ts
const { getCoordinatorAgents } =
require('../../coordinator/workerAgent.js') as typeof import('../../coordinator/workerAgent.js')这不是技术债。这是深思熟虑的架构选择,理由有二:
"Use lazy require inside the function body to avoid circular dependency issues at module init time. The coordinatorMode module depends on tools which depend on AgentTool which imports this file."ESM 的静态 import 在模块初始化时就会触发整条依赖链。当依赖形成环路时,你会得到 `undefined`。而 CJS 的 `require()` 是函数调用,延迟到运行时,此时模块已完成初始化。
const coordinatorModeModule = feature('COORDINATOR_MODE')
? require('./coordinator/coordinatorMode.js')
: null当 `COORDINATOR_MODE` 为 false 时,整个 `require()` 分支和它引用的模块树**在构建产物中完全消失**。静态 ESM import 做不到这么干净的 tree shaking。
同样反直觉的是记忆系统。跨会话记忆用的是**普通 markdown 文件**,不是向量数据库:
~/.claude/projects/{project}/memory/
├── MEMORY.md # 索引(<200 行,每次加载进 context)
├── user_role.md # 用户信息
├── feedback_testing.md # 行为偏好
└── project_deadline.md # 项目上下文每个文件有 YAML frontmatter 标注类型和描述,MEMORY.md 作为索引每次会话都加载进 system prompt。
**这给AI创业者的启发是什么?**
我们做 AI Infra 时,很容易陷入"最佳实践"的惯性——TypeScript 就该用 ESM,记忆就该用向量数据库。但 Claude Code 的选择提醒我:**技术决策的标准是"在具体约束下最优",而不是"在理论上最正确"。** `require()` 不优雅,但它解决了 ESM 解决不了的问题。Markdown 文件不高级,但 AI 天然能读写它,用户能直接审查它,不需要额外的 embedding pipeline。
四、最精巧的工程:同步临界区保护文件编辑
`src/tools/FileEditTool/FileEditTool.ts第 443-491 行是整个代码库中最让人欣赏的一段:
// 关键段:整个过程是同步的,没有 await,没有 yield
const content = readFileSync(path) // 1. 同步读取文件
if (mtime !== lastReadMtime) throw Error() // 2. 检查时间戳
const newContent = content.replace(old, new) // 3. 计算新内容
writeFileSync(path, newContent) // 4. 同步写入在一个几乎全是 async/await 的 Node.js 代码库中,这段刻意使用**同步 API**。原因很简单但至关重要:
**异步操作的任何 `await` 都是一个"让出控制权"的点。** 在 `readFile` 和 `writeFile` 之间如果有 await,用户在 IDE 中的手动编辑、另一个 Agent worker 的并发写入、甚至 IDE 的自动格式化,都可能在这个间隙修改文件。同步操作保证了 read-check-write 是原子的——没有其他 JavaScript 代码能在这几行之间执行。
围绕这个临界区,还有完整的校验链:
1. 文件自上次 Read 后是否被外部修改?(时间戳 + 内容双重校验)
2. old_string 是否唯一匹配?(防歧义替换)
3. 写入后通知 LSP 和 IDE(didChange, didSave)
4. 更新内存缓存,避免后续读到脏数据**这给AI创业者的启发是什么?**
AI Agent 的工具执行看似简单——不就是读写文件吗?但在生产环境中,Agent 操作的是用户正在编辑的文件,和 IDE、Git、其他 Agent 共享同一个文件系统。
这是一个典型的并发问题,但大多数 Agent 框架完全忽略了它。Anthropic 的解法不是复杂的分布式锁,而是利用 JavaScript 单线程模型的特性,用同步操作实现了事实上的互斥。
五、最被低估的能力:上下文预算管理
这可能是 Claude Code 中最不被注意但最有工程深度的子系统。当对话越来越长,token 数逼近上下文窗口上限时,Claude Code 有四级退化策略:
Level 1: Microcompact — 替换旧工具结果为摘要
Level 2: Context Collapse — 折叠早期对话轮次
Level 3: Snip Compact — 截断历史消息
Level 4: Reactive Compact — 紧急压缩 + 重试但真正精妙的是 **Content Replacement State**(`src/utils/toolResultStorage.ts`)。当工具结果超过 50KB 时,系统将完整内容持久化到磁盘,只给模型一个 2KB 的预览摘要。关键在于——**替换决策一旦做出就被"冻结"**:
// 如果模型已经看到了完整内容 → 永远不替换(否则缓存失效)
// 如果模型已经看到了摘要 → 永远用同一份摘要(字节级一致)
// 即使会话恢复,也要重建完全相同的替换决策原因是 Anthropic 的 prompt caching 依赖前缀匹配——如果你在恢复会话时对同一个工具结果用了不同的摘要文本,整个缓存前缀就会失效,导致几万 token 的额外计费。
还有一个巧妙的时间维度优化:当最后一条消息超过 60 分钟时,服务端的 prompt cache 已经过期(TTL 最长 1 小时),这时旧的工具结果留着也不省钱了。`src/services/compact/timeBasedMCConfig.ts` 会自动将它们替换为 `[Old tool result content cleared]`,节省下一次请求的 token 数。
**这给AI创业者的启发是什么?**
大多数 Agent 框架对上下文管理的策略是"满了就截断"。
Claude Code 告诉我们,上下文管理不是一个简单的裁剪问题,而是缓存一致性问题。
每次修改历史消息的内容,都可能导致数万 token 的缓存失效。
正确的策略是:把上下文当作一个有版本的缓存资产来管理,而不是一个可以随意编辑的字符串。
六、最值得借鉴的产品设计:Markdown 驱动一切
Claude Code 的 Agent 定义、Skill 定义、记忆存储、甚至权限配置,全部基于 Markdown + YAML frontmatter:
---
description: Fast agent for exploring codebases
tools: [Read, Grep, Glob]
model: claude-sonnet-4-6
---
You are a code exploration agent. Use Glob to find files by pattern,
Grep to search content, and Read to examine files...这不是偷懒,而是深刻的产品洞察:
1. AI 天然能读写 Markdown:这意味着 Claude 自己就能帮用户创建、修改 Agent 定义。你不需要一个配置 UI。
2. 用户能直接审查和版本控制:当你的 Agent 记住了什么,`cat MEMORY.md` 就能看到,`git diff` 就能追踪变化。对比一下向量数据库里的 embedding——你能审查一个 768 维的浮点数组吗?
3. 零基础设施依赖:不需要数据库、不需要 embedding 服务、不需要额外的运行时。一个文件系统就够了。
**这给AI创业者的启发是什么?**
AI Infra 总是本能地引入复杂的基础设施——向量数据库存记忆、JSON Schema 定义工具、配置中心管理 Agent 参数。
但 LLM 时代的一个根本性转变是:纯文本成了最好的接口格式:因为用户(AI)和开发者(人类)都能直接读写它。
Markdown 不是退步,而是在 AI 时代找到了新的最优解。
七、最具前瞻性的架构:SDK 先行,CLI 只是皮
Claude Code 看起来是一个 CLI 工具,但 `src/entrypoints/` 的目录结构揭示了真相:
src/entrypoints/
├── cli.tsx # CLI 入口(我们看到的 claude code)
├── sdk/ # Agent SDK(可编程接口)
│ ├── coreTypes.ts
│ ├── runtimeTypes.ts
│ ├── controlTypes.ts
│ └── settingsTypes.generated.ts
├── mcp.ts # MCP Server 模式
└── agentSdkTypes.ts # SDK 类型导出CLI 只是 SDK 的一个消费者。同一套 Agent 引擎可以被:
- 嵌入其他应用(Agent SDK)
- 作为 MCP Server 被其他 Agent 调用
- 通过 Bridge 与 VS Code / JetBrains 双向通信
这解释了代码中大量的 `getIsNonInteractiveSession()` 判断——SDK 模式下没有终端 UI,没有交互式权限提示,行为需要完全不同。
甚至连内置 Agent 的注册都区分入口:
// SDK 入口不需要 Claude Code Guide agent(那是教用户使用 CLI 的)
const isNonSdkEntrypoint =
process.env.CLAUDE_CODE_ENTRYPOINT !== 'sdk-ts' &&
process.env.CLAUDE_CODE_ENTRYPOINT !== 'sdk-py'
if (isNonSdkEntrypoint) {
agents.push(CLAUDE_CODE_GUIDE_AGENT)
}**这给AI创业者的启发是什么?**
做 AI 产品: 先写 SDK,再写 UI。
这不是过度设计——它是把核心逻辑和交互逻辑分开,让 Agent 引擎能在 CLI、Web、IDE 插件、甚至其他 Agent 中复用。Claude Code 能在半年内同时发布 CLI、VS Code 扩展、JetBrains 插件、Web 版和 Agent SDK,靠的就是这个架构基础。
结语:工程定义产品边界
Claude Code CLI 这 51 万行代码,最大的感受是:
Claude Code 的竞争壁垒不在模型能力,而在工程。
2600 行的 bash 安全检查、精确到工具级的缓存命中率监控、字节级一致的上下文替换策略、同步临界区保护的文件编辑——这些是"让 AI Agent 真正能用于生产"所需的工程量。
模型能力是地板,工程质量是天花板。
Agent 框架的竞争,本质上是工程深度的竞争
最后:
创业不易,这对Anthropic是不小的打击。
不是因为竞争对手能从中获得什么,Claude Code 的核心壁垒从来不是代码本身,而是模型和工程团队。
真正的损失,是几十位工程师的心血被迫公开,源码里能看到内部代号(KAIROS、tengu)、A/B实验参数、ANT-ONLY 的内部功能分支,以及他们尚未准备好公开的产品方向。
最后,source map 不该出现在公开 npm 包里,一个配置错误导致的泄漏,这是一个值得检查的提醒。
# 检查 bundle 是否意外包含了 source maps
npm pack --dry-run | grep ".map"