刚刚 Claude Code 源码泄露!我扒出了 11 个隐藏秘密

大家好,我是程序员鱼皮 👨🏻‍💻。

堪称全球最强的 AI 编程工具 Claude Code 的 50 多万行源码泄露了!一家以 AI 安全著称的公司,自己却在代码发布环节翻了车。

针对这件事,我只想说两个字:

感谢!

这份源码对学技术的同学来说简直是宝藏资源啊,过年了过年了!

那 Claude Code 源码具体是怎么泄露的?源码里有哪些精妙的设计?为什么它能做到 AI 编程工具的天花板?

下面我利用 AI 带大家一起学习,学会它优秀的设计后,你可以改进自己的 AI 项目、或者面试的时候吊打一下面试官。

⭐️ 本文对应视频版:https://bilibili.com/video/BV1ZB9EBmEAU

到底是怎么泄露的

Claude Code 是通过 npm 发布的,你可以把 npm 理解为程序员的「应用商店」,开发者把写好的工具打包上传到这里,别人一行命令就能安装。

正常情况下,代码上线之前会经过「压缩混淆」处理,变成一坨人类看不懂的东西。

但在开发的时候,为了方便定位 Bug,代码打包工具会自动生成一个叫 Source Map 的文件,它相当于一张「翻译对照表」,能把加密后的代码还原成原始版本。但是这个文件只能在开发环境用,上线时必须删掉!

结果 Anthropic 的工程师在发布 Claude Code 2.1.88 版本的时候,打包工具 Bun 默认生成了 Source Map 文件,而他们忘了在发布配置里把 .map 文件排除掉。于是一个 59.8 MB 的 cli.js.map 文件就这么被直接发到了公开的 npm 仓库。

这个 map 文件本质上是一个 JSON,里面有两个关键数组,sources 存着所有文件的路径,sourcesContent 存着每个文件的完整源码,两者一一对应。只要写个脚本解析一下这个 JSON,按路径把内容写出来,所有源码就原样还原了。

而且据说 map 文件里甚至还引用了一个 Cloudflare R2 存储桶的公开地址,点进去直接就能下载打包好的完整源码目录,连脚本都不用写。

安全研究员 Chaofan Shou 最先发现了这个问题并发到了 X 上:

然后几个小时之内 GitHub 上就冒出了好几个镜像仓库,源码传遍了整个互联网,其中有个仓库不到 1 天就快 10w Star 了(而且仓库里早就没有 Claude Code 源码了)......

更搞笑的是,去年 2 月 Claude Code 刚发布的时候就出过一模一样的事故,同样的错误犯了两次!

怕不是 Claude Code 团队的开发者 Vibe Coding 上瘾,连基本操作都忘了?

源码里都有什么

这次泄露的是 Claude Code 客户端的源码,包括 1906 个 TypeScript 源文件、大约 51.2 万行代码。涵盖了 Agent 循环引擎、40 多个内置工具的完整实现、系统提示词的组装逻辑、记忆系统、上下文压缩、权限管控机制,还有一堆没上线的隐藏功能。但是不包括服务端的模型训练代码和 API 后端逻辑。

整个项目用的是 React Ink 框架,简单说就是用 React 来写命令行界面,你可以理解为「在终端里写网页」。所以 Claude Code 的命令行交互才会比很多传统工具丝滑。

我用 AI 把它的架构从上到下梳理了一遍,大致分为 6 层:

1)最上面是 CLI 和界面层,你在终端里看到的所有交互都是这一层负责的

2)往下是 Agent 循环引擎,你可以把它理解为 Claude Code 的大脑,所有的决策都从这里发出

3)再往下是工具系统,40 多个内置工具加上 MCP 扩展

4)然后是记忆系统,解决 AI 聊着聊着就断片儿的老毛病

5)上下文压缩系统,解决 token 越来越贵、窗口越来越满的问题

6)最底层是权限和安全系统

接下来咱们结合源码,看看每个模块具体是怎么实现的,Claude Code 是怎么做到这么好用的?

一、Agent 循环

大家都知道现在的 AI 编程工具早已经不是简单的「一问一答」了,而是能连续执行、自主完成任务的 AI Agent。

可能有同学会觉得,这背后一定是什么特别复杂的架构,用了什么 AI Agent 开发框架、什么多 Agent 协作之类的吧?

但我打开负责核心对话循环的 query.ts 文件一看,好家伙,最核心的代码其实就是一个 while(true) 无限循环!

typescript 复制代码
// query.ts
async function* queryLoop(params: QueryParams, consumedCommandUuids: string[]) {
  let state: State = {
    messages: params.messages,
    toolUseContext: params.toolUseContext,
    autoCompactTracking: undefined,
    maxOutputTokensRecoveryCount: 0,
    hasAttemptedReactiveCompact: false,
    turnCount: 1,
    // ...
  }

  // eslint-disable-next-line no-constant-condition
  while (true) {
    // 1. 上下文压缩(防止 token 爆炸)
    // 2. 调用大模型,流式获取响应
    // 3. 解析模型返回的工具调用
    // 4. 执行工具,获取结果
    // 5. 把结果追加到对话历史
    // 6. 如果没有新的工具调用,结束;否则继续循环
  }
}

就是这么朴素的一个循环。每一轮迭代里,程序先做上下文压缩、再调用大模型拿到响应,如果模型说「我要用某个工具」就去执行,把结果追加到对话历史里,然后进入下一轮,一直循环工作直到活儿干完了才停下来。

这在 AI 领域叫 ReAct 机制(推理 + 执行),让 AI 自己形成「思考 → 行动 → 观察 → 再思考」的闭环。Claude Code 的源码就是这个理论最接地气的工程实现了。

每次循环中,程序会通过 services/api/claude.ts 里的 queryModel 函数来跟大模型沟通。这个函数负责发送请求、接收 SSE 流式输出、累计 token 用量这些事情。模型返回的结果里如果包含 tool_use 类型的内容,说明 AI 还想调用工具继续干活,循环就接着转;如果没有 tool_use 了,说明任务做完了,循环就结束。

typescript 复制代码
// services/api/claude.ts --- 模型调用核心函数
async function* queryModel(
  messages: Message[],
  systemPrompt: SystemPrompt,
  thinkingConfig: ThinkingConfig,
  tools: Tools,
  signal: AbortSignal,
  options: Options,
): AsyncGenerator<StreamEvent | AssistantMessage> {
  // 解析模型(含 Bedrock inference profile)
  // 合并 Beta headers
  // 构造 anthropic.beta.messages 流式请求
  // 累计 token 用量
  // yield 流式事件
}

在这个循环定义的上方,还有一段 Anthropic 工程师留下的注释,叫什么「巫师守则」:

typescript 复制代码
// query.ts --- queryLoop 函数上方
/**
 * The rules of thinking are lengthy and fortuitous...
 *
 * The rules follow:
 * 1. A message that contains a thinking block must be part of a query
 *    whose max_thinking_length > 0
 * 2. A thinking block may not be the last message in a block
 * 3. Thinking blocks must be preserved for the duration of an assistant
 *    trajectory
 *
 * Heed these rules well, young wizard. For they are the rules of thinking,
 * and the rules of thinking are the rules of the universe. If ye does not
 * heed these rules, ye will be punished with an entire day of debugging
 * and hair pulling.
 */

这三条规则是关于模型思考过程(thinking block)的处理约束,大意就是「年轻的巫师啊,好好遵守这三条关于 thinking block 的规则,不然你将受到整整一天 debug 和拔头发的惩罚」。

我一时间分不清,这是程序员的浪漫,还是 AI 的浪漫?

二、工具设计

Claude Code 内置了 40 多个工具,在负责工具注册和管理的 tools.ts 文件里,有个 getAllBaseTools() 函数列出了所有内置工具:

typescript 复制代码
// tools.ts --- 工具注册文件
/**
 * NOTE: This MUST stay in sync with https://console.statsig.com/...
 * in order to cache the system prompt across users.
 */
export function getAllBaseTools(): Tools {
  return [
    AgentTool,         // 子 Agent 生成
    TaskOutputTool,    // 任务输出
    BashTool,          // 执行终端命令
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    FileReadTool,      // 读文件
    FileEditTool,      // 编辑文件
    FileWriteTool,     // 写文件
    NotebookEditTool,  // 编辑 Notebook
    WebFetchTool,      // 网络请求
    WebSearchTool,     // 网络搜索
    TodoWriteTool,     // 待办事项
    SkillTool,         // 技能调用
    EnterPlanModeTool, // 进入规划模式
    ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
    // ... 还有几十个,根据环境和 Feature Flag 动态加载
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
  ]
}

注意开头那段注释,意思是这份工具清单必须和他们的 A/B 测试配置中心保持同步,否则全球用户共享的系统提示词缓存就废了。看来工具注册不只是功能问题,还直接关系到成本和性能。

还有最后一行的 ToolSearchTool,当用户接了很多 MCP 插件、工具数量特别多的时候,Claude Code 不会把所有工具的完整描述都塞进系统提示词里,而是先给模型一份「工具名 + 一句话介绍」的精简清单,让模型自己选需要哪些,再按需加载完整定义,从而节省了大量 token。

代码里还能看到 process.env.USER_TYPE === 'ant' 这个判断,ant 是 Anthropic 内部员工的代号(蚂蚁),这说明 Anthropic 自己也在重度用 Claude Code 来开发 Claude Code,怪不得这源码一股 AI 味儿。

每个具体的工具都是通过 Tool.ts 里的 buildTool 工厂函数创建的,这个函数有个很巧妙的 默认值设计

typescript 复制代码
// Tool.ts --- 工具类型定义和工厂函数
// Defaults (fail-closed where it matters):
// - isConcurrencySafe → false (assume not safe)
// - isReadOnly → false (assume writes)
// - isDestructive → false
// - toAutoClassifierInput → '' (skip classifier --- security-relevant tools must override)
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,
  isReadOnly: (_input?: unknown) => false,
  isDestructive: (_input?: unknown) => false,
  toAutoClassifierInput: () => '',
  // ...
}

注释里写的 「fail-closed where it matters」 是什么意思呢?

可以看到 isConcurrencySafeisReadOnly 都默认为 false,也就是说如果某个工具的开发者忘了声明「我只是读个文件,不会改东西」,系统就自动把它当成危险操作处理,不让它并发执行。

这种思路叫做 fail-closed(失败时关闭),打个比方就像公司的门禁系统,你没刷卡默认进不去;跟它相反的 fail-open 就像小区大门,谁来都让进。

对于 AI 应用,你不知道它下一步会调用什么工具。在这种不确定性下,「默认禁止」比「默认允许」安全太多了。毕竟 Claude Code 直接操作你整个代码库,一旦出事儿可能是你半个月的代码白写了。

还有 toAutoClassifierInput 默认返回空字符串,意味着默认跳过自动分类器检查,但注释专门强调了安全相关的工具必须自己重写这个方法。这也是 fail-closed 的思路。

三、读写分离的工具并发

假设 AI 同时想读 3 个文件、再改 1 个文件,这 4 个操作是一个一个排队跑、还是一起并发执行呢?

Claude Code 在负责工具执行编排的 toolOrchestration.ts 里做了一套很聪明的读写分离机制。

先看并发上限,默认最多 10 个工具同时并发,还可以通过环境变量调整:

typescript 复制代码
// services/tools/toolOrchestration.ts --- 工具执行编排
function getMaxToolUseConcurrency(): number {
  return parseInt(process.env.CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY || '', 10) || 10
}

然后是核心的分批逻辑:

typescript 复制代码
// services/tools/toolOrchestration.ts
function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
  return toolUseMessages.reduce((acc, toolUse) => {
    const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
    const parsedInput = tool?.inputSchema.safeParse(toolUse.input)
    const isConcurrencySafe = parsedInput?.success
      ? (() => {
          try {
            return Boolean(tool?.isConcurrencySafe(parsedInput.data))
          } catch {
            return false  // 抛异常也当不安全,又是 fail-closed
          }
        })()
      : false  // 解析失败也当不安全

    if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
      acc[acc.length - 1].blocks.push(toolUse)  // 合并到并发批次
    } else {
      acc.push({ isConcurrencySafe, blocks: [toolUse] })  // 新开一个批次
    }
    return acc
  }, [])
}

简单说就是连续几个只读工具可以一起跑,但一旦遇到写操作,就得排队等前面的都完成了才行。而且注意那个 try-catch,如果判断方法本身抛了异常(比如参数解析出错),也直接当不安全处理。

并发执行完之后,产生的上下文修改 contextModifier 也不会立刻生效,而是先排队存着,等一个批次的所有工具都跑完了再按顺序依次应用。

在工具执行编排文件的 runTools 函数里能看到这个逻辑:

typescript 复制代码
// services/tools/toolOrchestration.ts --- runTools 函数
if (isConcurrencySafe) {
  const queuedContextModifiers = {}
  for await (const update of runToolsConcurrently(blocks, ...)) {
    if (update.contextModifier) {
      // 先排队,不立即应用
      queuedContextModifiers[toolUseID].push(modifyContext)
    }
  }
  // 全部跑完后,按工具调用顺序依次应用
  for (const block of blocks) {
    for (const modifier of queuedContextModifiers[block.id]) {
      currentContext = modifier(currentContext)
    }
  }
}

学过数据库的同学肯定觉得眼熟,这不就是「读读并行、读写互斥」的简化版嘛。很多计算机底层的设计思想,换个场景照样管用,这就是基础知识的魅力。

四、System Prompt 的缓存分裂

Anthropic 的 API 支持 Prompt Cache 提示词缓存,如果你每次发给模型的系统提示词前半部分都不变,API 就能缓存这部分内容,后续请求直接复用,不用重新处理。

打开负责系统提示词组装的 constants/prompts.ts 文件,你会看到提示词被一个叫 DYNAMIC_BOUNDARY 的标记分成了上下两部分:

typescript 复制代码
// constants/prompts.ts --- 系统提示词组装
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
  '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'

sections = [
  // ...静态部分:角色定义、行为规范、工具说明、语气要求等
  // === BOUNDARY MARKER - DO NOT MOVE OR REMOVE ===
  ...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []),
  // --- Dynamic content (registry-managed) ---
  ...resolvedDynamicSections,  // 动态部分:当前时间、git 状态、CLAUDE.md 配置、MCP 工具等
]

Claude Code 用这条分界线精确地把提示词切成两半:上面是静态部分,全球几百万用户共享同一份缓存;下面是动态部分,比如你当前的时间、你的 Git 仓库状态、你自己配的 CLAUDE.md 规则等等,每个用户各不相同,独立加载。

源码注释里还专门警告,如果把动态内容不小心混到了静态部分里,每个用户的提示词都不一样了,缓存就全废了。

typescript 复制代码
// constants/prompts.ts
// WARNING: Do not remove or reorder this marker without updating cache logic in:
// - src/utils/api.ts (splitSysPromptPrefix)
// - src/services/api/claude.ts (buildSystemPromptBlocks)

如果你也在做 AI 应用,调用量大的话,这个优化技巧能帮你省不少钱。

五、内容检索策略

AI 模型本身没有你项目的代码记忆,所以需要一种方式让它「看到」相关的代码和文档。

现在业界最流行的做法叫 RAG 检索增强生成,简单说就是先把项目数据做 Embedding 存到向量数据库里,用户提问时先从数据库里检索出相关内容,再连同问题一起发给模型回答。

但没想到,Claude Code 压根不用这套!

不管是搜记忆文件还是搜历史对话,它用的都是最朴素的 Grep 文本搜索。

在负责记忆系统的 memdir/memdir.ts 文件里有个 buildSearchingPastContextSection 函数,写得很明确:

typescript 复制代码
// memdir/memdir.ts --- 记忆搜索指令
const memSearch = `grep -rn "<search term>" ${autoMemDir} --include="*.md"`
const transcriptSearch = `grep -rn "<search term>" ${projectDir}/ --include="*.jsonl"`

Claude Code 的创始人 Boris Cherny 在播客里说过:俺们试过 RAG,但发现让 AI 自己决定搜什么、怎么搜,效果反而远远好于 RAG。

我的理解是这样的,RAG 相当于你帮实习生把相关资料都整理好了打包给他看;而 Agentic Search 是直接给他公司文档库的权限,让他自己去找。模型能力越强,后者的优势就越大,因为 AI 比你更知道自己需要什么信息。而且 Grep 这种方案简单、没有索引过期问题、不用维护向量数据库,工程复杂度直接降了一个量级。

所以做 AI 应用的时候,哪些能力该交给工程系统、哪些该留给 AI 模型,这个边界值得好好想想。模型越来越强,很多以前需要复杂工程方案解决的问题,现在可能直接让 AI 自己搞定就行了。

六、三层记忆架构

这是我觉得整份源码里设计最精妙的部分了,也是现在 Context Engineering 上下文工程领域最值得学的案例。

用过 AI 编程工具的朋友应该都体验过:同一个对话框里聊久了,AI 就断片儿了,忘了之前说过什么,甚至自相矛盾。

Claude Code 为了解决这个问题,搞了一套分层记忆系统,网上有人管它叫 Self-Healing Memory 自愈记忆。

第一层、MEMORY.md(热数据,常驻加载)

MEMORY.md 就像一本书的目录,每次对话都会加载到上下文里。

看看 memdir/memdir.ts 文件里是怎么限制它的大小的:

typescript 复制代码
// memdir/memdir.ts --- 记忆索引文件
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
// ~125 chars/line at 200 lines. At p97 today; catches long-line indexes that
// slip past the line cap (p100 observed: 197KB under 200 lines).
export const MAX_ENTRYPOINT_BYTES = 25_000

最多 200 行、25KB,而且只存指针不存内容,每行不超 150 字符。因为它每次都要占上下文窗口的位置,如果太大就会把有用的对话内容挤掉。

注释里提到,25KB 那个字节限制是后来加的。因为他们发现有用户虽然没超 200 行,但每行写了一大堆,200 行居然能写到 197KB。所以不得不加个字节数的兜底保护。

如果内容超出限制了怎么办?

在这个文件里,还有一段详细的截断逻辑:

typescript 复制代码
// memdir/memdir.ts --- 截断逻辑
export function truncateEntrypointContent(raw: string): EntrypointTruncation {
  // 先按行数截断
  let truncated = wasLineTruncated
    ? contentLines.slice(0, MAX_ENTRYPOINT_LINES).join('\n')
    : trimmed
  // 再按字节数截断,在最后一个换行符处切,避免截断到一半
  if (truncated.length > MAX_ENTRYPOINT_BYTES) {
    const cutAt = truncated.lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)
    truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES)
  }

  // 截断后追加 WARNING,让 AI 知道这个文件没加载完
  return {
    content: truncated +
      `\n\n> WARNING: ${ENTRYPOINT_NAME} is ${reason}. Only part of it was loaded.`,
  }
}

采用行数截断 + 字节截断双保险,截断时还会在最后一个换行符处切开,不会切到一行的中间。截断之后还追加了一条 WARNING,让 AI 知道「这个索引没加载完整」。这种细节才是 Claude Code 体验好的原因。

第二层、话题文件(温数据,按需加载)

你的编码偏好、项目的架构约定、之前踩过的坑这些细节信息,都存在单独的话题文件里,比如 user_role.mdfeedback_testing.md

新对话开始时,Claude Code 会用 Sonnet 小模型来挑选最多 5 个跟当前对话相关的文件加载进来。在负责记忆召回的 findRelevantMemories.ts 文件中,可以看到它挑选记忆的提示词:

typescript 复制代码
// memdir/findRelevantMemories.ts --- 记忆召回
const SELECT_MEMORIES_SYSTEM_PROMPT = `You are selecting memories that will be
useful to Claude Code as it processes a user's query.
Return a list of filenames for the memories that will clearly be useful (up to 5).
- If a list of recently-used tools is provided, do not select memories that are
  usage reference or API documentation for those tools (Claude Code is already
  exercising them). DO still select memories containing warnings, gotchas, or
  known issues about those tools --- active use is exactly when those matter.`

最后那条规则我觉得是 punchline:如果某个工具正在被使用,就不加载它的使用文档(你都在用了说明你会用),但一定要加载它的已知问题和坑。想想也对,你正在用一个东西的时候,最怕的不就是不知道有什么坑嘛。

而且 Claude Code 的记忆 不记代码。因为代码会发生变化,但记忆并不会自动更新。举个例子,如果记忆里说「函数 X 在第 30 行」,你后来重构了,这条记忆就变成误导了。所以它只记人的偏好和判断,代码的事实永远去源码里实时读取。

做过后端开发的朋友都知道,缓存和数据库不一致是最坑的 Bug 之一。Claude Code 的做法等于从根源上消灭了不一致的可能性。

第三层、历史对话(冷数据,Grep 搜索)

更早之前的历史对话会被存成 .jsonl 格式的文件,需要的时候用 Grep 搜关键词就能找到。

总结一下,不同温度的数据用不同方式管理。热的常驻、温的按需、冷的搜索。

如果你面试被问到 "AI 应用怎么做长期记忆",这套方案绝对能让面试官眼前一亮,毕竟 Claude Code 这种级别的产品都在用。

七、五级上下文压缩

上一节讲的是记忆怎么存和取,这一节讲的是上下文怎么「瘦身」。大模型的上下文窗口是有上限的,随着对话越来越长、工具调用结果越攒越多,token 用量会快速膨胀,不仅费钱,还可能直接超出窗口限制导致请求失败。

Claude Code 为了应对这个问题,设计了五级压缩策略,像漏斗一样层层过滤:

  1. Snip 剪裁:最轻的一刀,把旧的工具调用结果只保留结构,不保留内容
  2. Microcompact 微压缩:把体积大的工具执行结果卸载到缓存里。注意是卸载到缓存而不是直接丢掉,因为子智能体后续可能还需要这些结果
  3. Context Collapse 折叠:对中间的对话做折叠摘要,只保留关键信息
  4. Autocompact 自动压缩:当上下文占用超过阈值时,触发全量摘要压缩
  5. Reactive Compact 应急压缩:最后的兜底,当 API 返回 413 "提示词太长" 错误时紧急触发

这五级从轻到重依次触发,能裁掉的内容先裁掉,实在不够了再上更重的方案。

query.ts 文件的开头可以看到其中三级压缩模块通过 Feature Flag 按需引入(另外两级在其他文件里):

typescript 复制代码
// query.ts --- 压缩模块按需加载
const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./services/compact/reactiveCompact.js') : null
const contextCollapse = feature('CONTEXT_COLLAPSE')
  ? require('./services/contextCollapse/index.js') : null
const snipModule = feature('HISTORY_SNIP')
  ? require('./services/compact/snipCompact.js') : null

这里面还有个「断路器」机制让我印象深刻,在负责自动压缩的 services/compact/autoCompact.ts 文件里:

typescript 复制代码
// services/compact/autoCompact.ts --- 断路器
// Stop trying autocompact after this many consecutive failures.
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures
// (up to 3,272) in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

看到注释中的那串数字了吗?

2026 年 3 月 10 号他们统计发现有 1279 个会话连续压缩失败了 50 次以上,最夸张的一个会话连续失败了 3272 次还在不停重试,全球每天浪费 25 万次 API 调用!

要是让我这种老倒霉蛋遇上了,光 token 费就够我吃一个月的了,这要是曝光出来公关团队怕是有的忙了......

所以他们加了这个断路器,连续失败 3 次就自动停下来。

八、安全审查

用 Claude Code 的时候你可以开启 --dangerously-skip-permissions 模式(也叫 YOLO 模式),跳过所有权限确认,让 AI 自己干活。

我之前一直以为这模式就是完全不设防了,看了源码才知道,其实背后还偷偷跑着一个「影子 AI」在帮你把关。

源码里有个文件叫 utils/permissions/yoloClassifier.ts,每次主 AI 要执行操作,这个独立的 AI 分类器都会过一遍:

typescript 复制代码
// utils/permissions/yoloClassifier.ts --- YOLO 安全分类器
export async function classifyYoloAction(
  toolName: string,
  toolInput: Record<string, unknown>,
  // ...
): Promise<'allow' | 'soft_deny' | 'hard_deny'> {
  // 用一个独立的模型来判断这个操作安不安全
  // allow: 安全的,直接放行
  // soft_deny: 有风险,降级成需要手动确认
  // hard_deny: 直接拦截,没得商量
}

而且权限系统不只有 YOLO Classifier 一个检查点。一次工具调用要过的关卡至少包括:

  1. 你当前的运行模式(Plan / Auto / Bypass)
  2. 用户在 hooks 里设的自定义规则
  3. YOLO Classifier 的模型分析
  4. Bash 命令危险度分类(像 rm -rf 这种直接拦截)
  5. 配置文件里的规则引擎

多个来源的权限结果还有竞争关系,最终取最严格的那个。

其中第 4 点 Bash 命令安全检查的严格程度远超我的预期。在 tools/BashTool/bashSecurity.ts 这个文件里,定义了 23 种 安全检查规则:

typescript 复制代码
// tools/BashTool/bashSecurity.ts --- Bash 安全检查 ID
const BASH_SECURITY_CHECK_IDS = {
  INCOMPLETE_COMMANDS: 1,       // 残缺命令(以 tab 或 - 开头)
  JQ_SYSTEM_FUNCTION: 2,       // jq 的 system() 函数调用
  OBFUSCATED_FLAGS: 4,         // 混淆的命令行参数
  SHELL_METACHARACTERS: 5,     // 危险的 shell 元字符
  DANGEROUS_VARIABLES: 6,      // 危险环境变量注入
  IFS_INJECTION: 11,           // IFS 变量注入
  PROC_ENVIRON_ACCESS: 13,     // /proc/environ 访问
  CONTROL_CHARACTERS: 17,      // 控制字符
  UNICODE_WHITESPACE: 18,      // Unicode 空白字符欺骗
  ZSH_DANGEROUS_COMMANDS: 20,  // Zsh 危险命令(zmodload 等)
  COMMENT_QUOTE_DESYNC: 22,    // 注释与引号状态不同步
  QUOTED_NEWLINE: 23,          // 引号内换行
  // ... 共 23 种
}

比如第 18 条的 UNICODE_WHITESPACE,说明 Anthropic 的安全团队考虑过用 Unicode 零宽空格来混淆命令的攻击手法,让安全检查器看到的和 shell 实际执行的不是同一条命令。还有第 20 条针对 Zsh 的 zmodload 命令,这个命令能加载模块绕过常规的文件和网络检查,总之防护做的还是很周到的。

怪不得我开着全部放行模式用了这么久,从来没出过什么严重事故,原来是有一套多层机制在背后替我负重前行。

九、Feature Flag 和未来功能

在 Claude Code 的源码中,你会经常看到 feature('XXX') 的判断:

typescript 复制代码
// tools.ts --- Feature Flag 条件加载
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
  ? require('./tools/SleepTool/SleepTool.js').SleepTool : null

const WebBrowserTool = feature('WEB_BROWSER_TOOL')
  ? require('./tools/WebBrowserTool/WebBrowserTool.js').WebBrowserTool : null

这就是 Feature Flag 功能开关,做开发的同学应该都不陌生。每个实验功能都藏在开关后面,可以按用户、按环境灰度发布。这样万一出了问题,就不用回滚代码,直接关掉开关就行。通过条件 require,关掉的功能在打包时还能被 tree-shaking 掉,不增加体积。

有趣的是,这些 Feature Flag 等于无意中泄露了 Claude Code 的产品路线图:

1)KAIROS:长期助手模式,AI 可以 24 小时持续运行不结束。里面还有个 AutoDream 自动做梦功能,名字起得很是浪漫,说白了就是 AI 白天干活记笔记,晚上自动整理记忆。

2)COORDINATOR_MODE:多 Agent 协作,一个 AI 指挥多个 AI 干活。

coordinatorMode.ts 能看到,它的工作流分四个阶段:

typescript 复制代码
// coordinator/coordinatorMode.ts --- 协调者工作流定义
| Phase          | Who                | Purpose                                              |
|----------------|--------------------|---------------------------------------------------------|
| Research       | Workers (parallel) | Investigate codebase, find files, understand problem  |
| Synthesis      | You (coordinator)  | Read findings, craft implementation specs             |
| Implementation | Workers            | Make targeted changes per spec, commit                |
| Verification   | Workers            | Test changes work                                     |

子智能体之间通过 utils/mailbox.ts 里的 Mailbox(一个基于文件的消息队列)来通信。

还有 VOICE_MODE 语音模式、WEB_BROWSER_TOOL 浏览器操作工具等等。

十、反蒸馏和卧底模式

Anthropic 为了防止竞争对手窃取 Claude Code 的能力、以及防止内部信息通过开源贡献泄露,在源码里埋了两套防御机制。(但没想到这波泄露了个大的 😂)

先说反蒸馏。有些竞争对手可能会通过录制 Claude Code 的 API 流量来「蒸馏」它的能力,就是把大模型的输入输出录下来,用来训练自己的小模型。

Anthropic 的应对策略简直绝了,直接往 API 请求里注入假工具定义:

typescript 复制代码
// services/api/claude.ts --- 负责模型 API 调用
// Anti-distillation: send fake_tools opt-in for 1P CLI only
if (feature('ANTI_DISTILLATION_CC')
    && process.env.CLAUDE_CODE_ENTRYPOINT === 'cli'
    && shouldIncludeFirstPartyOnlyBetas()
) {
  result.anti_distillation = ['fake_tools']
}

做防御不是单纯加密或者限速,而是主动给你喂假数据,让你拿去训练的模型越训越差,有点儿反间计的意思。

而且它只在 Anthropic 官方的命令行客户端,也就是你在终端里直接用 claude 命令的场景中才生效,通过 SDK 或其他方式接入的不受影响。

再说卧底模式,Anthropic 内部员工用 Claude Code 往开源项目提交代码时,会自动启用这个模式:

typescript 复制代码
// utils/undercover.ts --- 负责隐藏 AI 贡献痕迹
/**
 * Undercover mode --- safety utilities for contributing to public repos.
 * When active, Claude Code strips all attribution to avoid leaking internal
 * model codenames, project names, or other Anthropic-internal information.
 * The model is not told what model it is.
 *
 * There is NO force-OFF. This guards against model codename leaks.
 */

export function isUndercover(): boolean {
  if (process.env.USER_TYPE === 'ant') {
    if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return true
    // Auto: active unless we've confirmed we're in an internal repo.
    return getRepoClassCached() !== 'internal'
  }
  return false
}

注释里写得很明确:「There is NO force-OFF」,不能强制关闭。而且从代码逻辑看,默认就是开启的,只有当仓库被确认为内部仓库时才会关掉。说明 Anthropic 非常担心内部模型代号通过开源贡献泄露出去。

十一、其他细节

最后分享几个源码里藏着的有趣细节。

1)在 buddy/ 目录下,Anthropic 的工程师居然偷偷藏了一套数字宠物系统!

一共 18 种物种,从鸭子到仙人掌应有尽有:

typescript 复制代码
// buddy/types.ts --- 虚拟宠物物种定义
export const SPECIES = [
  duck, goose, blob, cat, dragon, octopus, owl, penguin,
  turtle, snail, ghost, axolotl, capybara, cactus, robot,
  rabbit, mushroom, chonk,
] as const

这个功能原本计划 4 月份作为彩蛋预热、5 月正式上线,结果源码泄露了提前被大家发现了。AI 写代码写累了,然后撸撸 AI 宠物,Anthropic 的工程师也太会整活了。

2)还有个小细节,因为 Claude 之前老是浪费一轮对话去执行 mkdir 检查目录存不存在,工程师们直接在 memdir/memdir.ts 里硬编码了一行提示,告诉 AI "这个目录已经存在了,直接用 Write 工具往里写就行,不要再跑 mkdir 或者检查目录是否存在了!"

typescript 复制代码
// memdir/memdir.ts
// Shipped because Claude was burning turns on `ls`/`mkdir -p` before writing.
export const DIR_EXISTS_GUIDANCE =
  'This directory already exists --- write to it directly with the Write tool
  (do not run mkdir or check for its existence).'

3)Claude Code 对启动速度有一种近乎偏执的追求。

比如在入口文件 cli.tsx 里,--version 这种最简单的请求一个模块都不加载,直接返回;其他路径全部用 await import() 动态加载,按需引入。

更绝的是,在加载主模块的这几百毫秒里,它会同时启动一个「早期输入捕获器」(utils/earlyInput.ts),把你在等待时敲的键都缓存起来,模块加载完之后再回放,让你感觉 "刚敲完就有反应了"。

初始化阶段还会偷偷发一个 TCP 预连接(utils/apiPreconnect.ts),让 TCP+TLS 握手和初始化工作并行跑,省掉 100ms 左右的等待时间。

typescript 复制代码
// utils/apiPreconnect.ts --- TCP 预连接
/**
 * Preconnect to the Anthropic API to overlap TCP+TLS handshake with startup.
 * The TCP+TLS handshake is ~100-200ms that normally blocks inside the first
 * API call. Kicking a fire-and-forget fetch during init lets the handshake
 * happen in parallel with action-handler work.
 */

最后聊几句

看完这份源码,你会发现 Claude Code 里其实没什么惊天动地的新算法,用的基本上都是程序员接触过的基础知识,比如并发控制、读写分离、分层缓存、断路器、功能开关等等......

但是 Claude Code 团队把这些东西组合到了 AI 场景里,打造出了一个极其优秀的产品,所以说计算机的基础知识和设计思想很重要。

这份源码也是目前最好的 AI 应用架构教材了。像 Agent 循环怎么写、工具系统怎么设计、记忆怎么管理、安全怎么做,答案全在里面。简历上如果能写上 "深度阅读了 Claude Code 源码并应用于自己的项目",那绝对是加分项。

至于说这次泄露对 Claude Code 有多大影响嘛...... 我觉得其他做 AI 编程工具的竞争对手们肯定开心坏了,大家也学到知识(吃到瓜)了,我也有流量了,用 Claude Code 的人更多了,可以说是皆大欢喜。

而且就算有人家的源码,也很难跟人家这种有资源的专业团队去竞争呀,你就像我开源出来一个 yupi-code 估计也没人用吧?

话说我最近正好在自己的 鱼皮 AI 导航网站 上更新 Claude Code 相关的教程,万万没想到竟然从源码开始讲起了。如果你想更系统地学习 AI 编程工具的使用方法、项目实战、经验技巧和原理,可以关注一波,后续持续更新。

相关推荐
sg_knight2 小时前
使用 Claude Code 写单元测试的实战方法
单元测试·log4j·ai编程
可观测性用观测云2 小时前
Claude Code 意外开源:我们看到了每一个企业级 Agent 都需要行为分析
ai编程·监控
Thomas.Sir3 小时前
第六章:RAG知识库开发之【深入浅出RAG使用效果评估:从指标到实践】
人工智能·python·ai·rag·效果评估
Agent产品评测局3 小时前
企业采购自动化落地,供应商全生命周期管控实现方案:智能体驱动下的全链路提效与合规治理
运维·人工智能·ai·chatgpt·自动化
小哈里3 小时前
【证书】2026上海市人工智能训练师—高级/三级考试介绍与复习(SAIA版)&&(零基础速通必过版)
人工智能·ai·大模型·证书·人工智能训练师
vivo互联网技术3 小时前
Nanobot(OpenClaw 轻量实现)的底层原理解析
ai·agent·openclaw·nanobot
得物技术3 小时前
日志诊断 Skill:用 AI + MCP 一键解决BUG|得物技术
运维·后端·程序员
Hui Baby3 小时前
大模型并发GPU
ai