AI测试 AI 赋能测试实践 06:当 51 万行源码裸奔,我们该如何给最强 AI 智能体 Claude Code 做测试(上)

EternalRights · 2026年04月20日 · 189 次阅读

前言

        2026年3月31日,安全研究员 Chaofan Shou(@Fried_rice)在 X 上发了一条推文:

"Claude code source code has been leaked via a map file in their npm registry!"

        就这一句话,Anthropic 的核心产品 Claude Code 的 51.2 万行 TypeScript 源代码,被完整地摊在了所有人面前。

        起因荒诞得像个段子:Anthropic 在发布 Claude Code 的 npm 包时,Bun 打包器的一个 bug 导致 .map 文件被意外打包进发行版。而这个 source map 文件指向的,是 Anthropic R2 存储桶中未混淆的 TypeScript 源码。结果就是,一个 npm install @anthropic-ai/claude-code 就能把整套源码拉下来。

        这不是一次开源发布,而是一次意外走光。

        而作为一个测试人员,我的第一反应不是"又要抄作业了",而是一个更根本的问题:

面对这样一个拥有 40+ 工具、50+ 命令、多层权限系统、多 Agent 协调能力的 AI 智能体,我们到底该怎么测它?

        这个问题比看起来要难得多。因为 Claude Code 不是传统意义上的软件——它是一个会思考、会调用工具、会在运行时做决策的自主代理。测一个函数的输入输出,和测一个会自己决定"要不要执行 rm -rf"的系统,完全不是一回事。

        所以这个系列会拆成三篇来写:

  • 上篇:先搞清楚 Claude Code 到底是什么,它的架构长什么样,有哪些值得测的东西
  • 中篇:从测试视角出发,设计一套完整的测试用例体系——覆盖功能、安全、性能、多 Agent 协同等维度
  • 下篇:落地执行——用什么框架、怎么搭环境、怎么跑自动化,以及一些我在实践中的真实踩坑

一、Claude Code 是什么?不是什么?

        先说不是什么。它不是一个聊天机器人前端,不是一个简单的 CLI wrapper,也不是一个"调 API 然后返回结果"的工具。

        Claude Code 是一个自主代理运行时(Autonomous Agent Runtime)。它的核心能力是:

  1. 接收用户指令
  2. 自主规划执行路径
  3. 在执行过程中调用各种工具(文件读写、Shell 命令、Web 搜索、子 Agent 派遣等)
  4. 根据工具执行结果做判断,决定下一步
  5. 循环 2-4,直到任务完成

        这个循环在 AI 领域有个名字:ReAct 循环(Reasoning + Acting)。Claude Code 的 query.ts 就是这个循环的核心实现——一个 1700 多行的 AsyncGenerator,在 while(true) 里不断迭代。

        如果你是一个测试人员,读到"while(true)"和"自主决策"这两个关键词,后背应该开始冒冷汗了。因为这意味着:

  • 执行路径不可枚举:同一个输入,不同运行可能走完全不同的路径
  • 结果不可预测:LLM 的非确定性 + 工具调用的副作用 = 你没法用传统断言去验证
  • 边界条件是模糊的:什么算"任务完成"?什么算"应该拒绝执行"?这比"返回 200 还是 500"要复杂得多

        但别慌,先把它拆开看看。


二、源码架构拆解:51 万行代码里藏着什么

        我克隆了泄露的源码仓库(jarmuine/claude-code,1903 个文件),做了完整的架构分析。以下是站在测试视角最值得关注的模块。

2.1 四层架构

┌──────────────────────────────────────────────┐
│           main.tsx(入口层)                    │
│   Commander.js CLI → 初始化 → REPL/Headless    │
├──────────────────────────────────────────────┤
│      QueryEngine / query.ts(代理循环层)        │
│   用户输入 → API调用 → 工具执行 → 响应 → 循环   │
├──────────────────────────────────────────────┤
│      services/tools/(工具执行层)               │
│   StreamingToolExecutor → toolExecution → hooks │
├──────────────────────────────────────────────┤
│  tools/ + hooks/toolPermission(工具+权限层)    │
│   每个工具:schema → validate → permission → call│
└──────────────────────────────────────────────┘

        入口层main.tsx,4684 行)负责 CLI 参数解析、环境初始化、REPL 渲染。它的启动流程做了很多并行预取——MDM 设置、Keychain 读取、GrowthBook 初始化都在模块加载期并行触发。如果你要测启动性能,这里就是瓶颈所在。

        代理循环层query.ts,1730 行 + QueryEngine.ts,1296 行)是整个系统的心脏。query() 函数是一个 AsyncGenerator,每次循环迭代都经历:上下文裁剪(snip/microcompact/autocompact)→ 调用 LLM API → 解析响应中的工具调用 → 执行工具 → 将结果喂回 LLM → 继续循环。

        工具执行层services/tools/)处理具体的工具调用流程。核心是 StreamingToolExecutor,它支持工具并发执行——并发安全的工具(如文件读取)可以并行运行,不安全的(如文件写入)串行排队。还有一个 runTools() 函数用于非流式场景,逻辑类似但更简单。

        工具 + 权限层是测试人员最该关注的地方。每个工具都有完整的生命周期:Schema 校验(Zod v4)→ validateInput()checkPermissions() → 用户确认/拒绝 → call() → PostToolUse hooks。

2.2 工具全景:40+ 工具,各有脾气

        从 tools.ts 可以看到完整的工具注册表。以下是按风险等级分类的关键工具:

风险等级 工具 能力
高危 BashTool 执行任意 Shell 命令(158KB,最大的单工具)
高危 FileWriteTool 创建/覆盖文件
高危 FileEditTool 修改文件内容
高危 AgentTool 派遣子 Agent(230KB,最复杂的工具)
中危 WebFetchTool 抓取 URL 内容
中危 WebSearchTool 网页搜索
低危 FileReadTool 读取文件
低危 GlobTool 文件搜索
低危 GrepTool 内容搜索
特殊 TodoWriteTool 任务管理
特殊 EnterPlanModeTool / ExitPlanModeTool 模式切换
特殊 SendMessageTool Agent 间通信
特殊 TeamCreateTool / TeamDeleteTool 多 Agent 团队

        注意 BashTool——它一个文件就 158KB,是所有工具中最大的。它的权限系统 (bashPermissions.ts,99KB) 和安全检查 (bashSecurity.ts,103KB) 加起来比它的核心逻辑还大。这说明 Anthropic 自己也知道,给 AI 一个 Shell 是多么危险的事情。

2.3 权限系统:六层防线

        Claude Code 的权限系统可能是目前 AI Agent 中最复杂的实现。从源码来看,它有六层防线:

第一层:工具注册过滤

// tools.ts
export function filterToolsByDenyRules(tools, permissionContext) {
  return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))
}

        在模型看到工具列表之前,就被权限规则过滤了。deny 规则匹配的工具直接从列表中消失,模型压根不知道它们存在。

第二层:Zod Schema 校验

// toolExecution.ts
const parsedInput = tool.inputSchema.safeParse(input)
if (!parsedInput.success) {
  // 返回 InputValidationError
}

        模型输出的工具参数先过一遍 Zod 校验。注释里写得坦白:"surprisingly, the model is not great at generating valid input"。

第三层:业务逻辑校验(validateInput)

        每个工具可以实现自己的 validateInput() 方法,做更深层的业务规则检查。比如 FileEditTool 会检查文件是否在写保护路径下。

第四层:权限检查(checkPermissions + canUseTool)

        这是最复杂的一层。权限决策可能来自:

  • 配置规则(allow/deny/ask)
  • 分类器(yoloClassifier,一个独立的 LLM 调用来判断命令是否安全)
  • Bash 分类器(bashClassifier,专门针对 Shell 命令的安全分类器)
  • 用户交互(弹出确认对话框)
  • Hooks(PreToolUse/PostToolUse 钩子)
  • 协调器模式(coordinator handler)
  • Swarm Worker 模式(swarm handler)

第五层:Sandbox 沙箱

        BashTool 有沙箱机制。shouldUseSandbox.ts 决定命令是否在沙箱中执行,沙箱通过 SandboxManager 实现。

第六层:安全检查(bashSecurity.ts)

        这是 BashTool 专属的深层安全防线,2593 行代码,检测各种注入手法:

  • Shell 元字符注入
  • Zsh 危险命令(zmodload、emulate 等)
  • 命令替换($()、反引号)
  • 进程替换(<()、>())
  • IFS 注入
  • Unicode 空白字符混淆
  • 注释/引号反同步攻击
  • 控制字符注入

        这套安全检查是纵深防御的典型做法。注释里有个很有意思的细节:它专门防御了 PowerShell 注释语法 <#,理由是"Added as protection against future changes that might introduce PowerShell execution"——说明他们在做安全设计时考虑了未来演进。

2.4 上下文管理:对话压缩的多级策略

        AI Agent 有一个传统软件没有的问题:上下文窗口是有限的。Claude Code 的解决方案是一套多级压缩策略:

  1. Snip CompactsnipCompact.ts):最激进,直接裁剪历史消息
  2. Microcompact:轻量级压缩,保留关键信息
  3. Context Collapse:分层折叠,在 REPL 中保留完整视图但 API 调用时只发送摘要
  4. Auto Compactcompact.ts,1706 行):当 token 使用接近上限时自动触发,用 LLM 生成对话摘要

        这套压缩策略直接影响 Agent 的行为质量——压缩太激进会丢失关键信息,导致任务执行出错;压缩太保守会触发 token 限制。这是一个需要仔细测试的平衡点。

2.5 多 Agent 协同

        AgentTool(230KB)支持派遣子 Agent 执行任务。子 Agent 可以:

  • 在前台或后台运行
  • 使用不同的模型(sonnet/opus/haiku)
  • 在隔离的 git worktree 中工作
  • 通过 SendMessageTool 互相通信
  • 组成 Team(TeamCreateTool/TeamDeleteTool

        coordinator/ 目录还有一个协调器模式,支持 coordinator-worker 架构——一个主 Agent 负责调度,多个 worker Agent 并行执行。

        多 Agent 协同带来的测试挑战是指数级的:2 个 Agent 互相发消息,3 个 Agent 在 worktree 里同时改文件,coordinator 给 worker 派活时 worker 挂了怎么办……这些都是真实的测试场景。


三、为什么测 AI Agent 和测传统软件不一样

        搞清楚 Claude Code 的架构后,我需要说清楚一个关键问题:为什么不能直接套用传统测试方法?

3.1 非确定性

        传统软件:add(2, 3) 永远返回 5

        AI Agent:同一个 prompt "帮我重构这个函数",每次执行可能走不同的路径——读不同的文件、搜索不同的关键词、生成不同的重构方案。你不能用 assert result == expected 这种断言。

3.2 工具调用的副作用

        传统软件的函数调用是纯计算(大部分情况)。AI Agent 的工具调用是有副作用的——它会真的创建文件、执行命令、发送网络请求。测试完你得清理环境,否则测试之间会互相污染。

3.3 执行路径的组合爆炸

        Claude Code 有 40+ 工具,每个工具有多种参数组合。一次对话可能产生多轮工具调用,每轮调用的结果又影响下一轮的决策。路径数是组合爆炸的。

3.4 权限系统的模糊边界

        "这个 Bash 命令该不该放行?"——有时候连人类都很难给出明确答案。yoloClassifier 用另一个 LLM 来判断,但 LLM 的判断本身也是非确定性的。你没法写一个简单的 pass/fail 测试用例。

3.5 环境依赖

        Claude Code 的行为高度依赖运行环境:操作系统、Shell 类型、git 状态、项目结构、MCP 服务器配置……在 CI 里跑和在实际用户机器上跑,行为可能完全不同。


四、测试思维的根本转变

        面对这些差异,测试思维需要从"验证实现"转向"验证行为"。

        传统测试的思维模型是:

输入 → [系统] → 输出
断言:输出 == 预期

        AI Agent 测试的思维模型应该是:

任务 → [Agent] → 行为轨迹
断言:
  - 轨迹是否安全?(没有越权操作)
  - 轨迹是否合理?(没有无意义的循环)
  - 结果是否有效?(任务确实完成了)
  - 资源消耗是否合理?(token 使用、执行时间)

        这个转变意味着我们需要从四个维度设计测试:

  1. 功能正确性:Agent 能不能完成任务?
  2. 安全性:Agent 会不会做不该做的事?
  3. 效率:Agent 会不会浪费资源?
  4. 鲁棒性:出错了 Agent 能不能恢复?

        下篇会围绕这四个维度,给出具体的测试用例设计。


后言

        Claude Code 的源码泄露,说到底就是一次构建管线安全事故。一个 source map 文件的配置失误,导致整个产品的知识产权裸奔。这件事暴露了 AI 公司在软件工程实践上的短板——工程化能力还没跟上模型能力的迭代速度。

        而从源码内容来看,还有几个值得测试人员关注的发现:

  1. 没有测试代码:整个仓库里找不到任何 .test.ts.spec.ts 文件。这意味着要么测试在另一个仓库,要么 Anthropic 内部对 Claude Code 的测试主要依赖手工。考虑到代码的规模和复杂度,后者更令人不安。

  2. 大量的防御性编程:bashSecurity.ts 有 2593 行安全检查,bashPermissions.ts 有 2622 行权限逻辑。这些代码的存在说明安全问题是在持续暴露后逐条修补的,不是系统化设计的。这种"打地鼠"式的安全开发,本身就是测试需要重点关注的区域。

  3. Feature Flag 泛滥:代码里到处都是 feature('XXX') 的条件分支。光是工具注册就有 PROACTIVE、KAIROS、AGENT_TRIGGERS、MONITOR_TOOL、WEB_BROWSER_TOOL 等十几个 Feature Flag。每个 Flag 组合都可能产生不同的行为——这对测试覆盖率是噩梦。

  4. 调试器检测:main.tsx 里有检测调试器的代码,发现调试器直接 process.exit(1)。Anthropic 把防逆向作为安全策略的一部分——而泄露事件恰好说明,这种策略有多脆弱。

        下一篇,我们将从功能、安全、性能、多 Agent 协同四个维度,给出可直接使用的测试用例设计。包括具体的测试场景、预期行为、以及如何处理非确定性断言。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册