上一篇,我分享了一个在真实场景中的复杂 Bug,涉及到好几个软件,以及 Windows 的系统机制。
我觉得这个例子非常适合作为测试例子,就让 Opus 5 分析了这个 Bug 的前因后果,并且根据这个机制设计了一个测试用例。
本来以为天衣无缝,没想到用 DeepSeek V4 Flash 测试了一下秒破,但是我让它在真实环境中分析了一轮,却完全找不到方向。
😄有些人天天说测试前端没意思,今天测试一个有意思的。就看你能不能看懂了!
我要把背景知识完全说清楚也有点难。
难不是坏事情,是你进步的时候到了。所以我要迎难而上,尝试把这个问题理清楚。
1、起因
这个问题的起因是,我想使用一个叫"牛来 Ox"的模型。因为 OpenCode 上免费提供了这个模型,所以我就准备在这里白嫖。但是在我使用这个软件的时候遇到了一个问题:一直加载中......
白嫖不成,五内俱焚!相当难受!

因为我对这个软件不熟悉,我也不确定这是模型有问题,还是软件有问题,还是网络有问题。
反正就是发什么都显示,在加载中......
为了解决这个问题,我问了三个 Agent + Model。其中一个是 Claude,一个是 ZCode,一个是 DeepSeek......结果有人找到了关键,有人找错了方向,有人绕了很久终于到了关键。具体结果我先不说!
2、分析
因为 Claude Code 分析最高效,简洁、清晰。所以我就贴一下它的分析过程!

它大概只花了 12 分钟就找到了问题的关键。12 分钟中大部分是测试延迟。
我们先看结论:
你在
C:\Users\tony(用户主目录)里启动了 OpenCode
也就是说,我这个问题,只是因为我在"用户目录"启动了 OpenCode,才导致任何请求都没有回应。
然后可以看下它的分析过程:
markdown
我来排查。先看看 opencode 的安装、配置和日志。
Ran 5 commands
确认了:你用的模型是 opencode-go 的 `ox-alpha-free`(opencode zen 的免费 Ox)。继续查进程和网络。
Ran 10 commands
现在直接用你本地的凭据探测 `ox-alpha-free` 的真实端点:
Ran 5 commands
返回了 **0 字节** ------ 这就是"一直加载中"。缩小触发条件:
对比测试触发挂起的条件
三次都通了------说明刚才那次是**间歇性挂起**。做多次采样确认稳定性:
6 次采样统计稳定性与 content 输出
模型可用但极慢。最后测一项决定性的:opencode 的 build agent 必须用 **工具调用**:
Ran 3 commands
没有 git 卡住、没有 lock。最后做决定性的 A/B:直接用 opencode CLI 在一个干净小目录里跑同一个模型。
在干净目录用 opencode CLI 测试该模型
干净目录 8 秒正常。现在对照------在你实际启动 opencode 的目录 `C:\Users\tony`(用户主目录)跑同一条命令:
在用户主目录对照测试
复现成功。再量化一下主目录规模作为佐证:
Ran 4 commands
排查完成,根因找到了,**不是软件坏了,也不是网络问题,更不是模型的问题**。
Opus 5 抓问题还是很准的,期间它还遇到了一个网络抖动导致的错误(极具误导性),但是它通过反复验证排除了这个问题。最后通过 A/B 对照实验快速定位了问题。
下面是 Claude Code 的结论:
我做了一组对照实验,用的是完全相同的命令和同一个 ox-alpha-free 模型:
| 启动目录 | 结果 |
|---|---|
| 干净的空目录 | 8.2 秒,正常回复"你好" |
C:\Users\tony |
150 秒超时被杀,零输出 ← 完全复现你说的"一直加载中" |
原因:OpenCode 每次发消息前,要对当前工作目录做文件监听(watcher)+ Git 快照(snapshot)+ 项目索引。你的主目录下有 81 万+ 个文件(其实是 100 多万),这个准备阶段根本跑不完,所以永远到不了调用模型那一步。
日志完全印证这一点:
当前那个 opencode 进程(PID 40880,directory=C:\Users\tony,已跑 6.5 小时、吃掉 406 秒 CPU / 520MB 内存)从 11:25 启动至今,日志里一条 stream / process session 记录都没有 ------ 请求压根没发出去。而 GUI 版正常,是因为它打开的是具体项目目录。
通过我抽象的描述,很快找到了问题关键。这能力确实强!
最强的部分在于它知道做 A/B 对照实验,想到用户目录这个问题。因为我的问题描述中并没有说启动路径,我也完全没想到,不同的启动路径会导致这个问题。
它为什么能想到这点,我始终是没有理解。可能就是因为它见多识广吧!
它除了告诉我问题的本质之外,也给了我修复方案!
但是,我突然不想让它修复这个问题。我对它提出了另外一个需求,让它帮我复刻当前这个 Bug,制造一个模拟的测试环境,方便我以后批量化自动化测试其他模型和智能体!
为了制作这个模拟环境,我和它研究了好久,因为有很多需要模拟的地方,有很多边界问题,也要防止其它模型能从我们的设计中猜出答案。
最后终于做出来了一个测试环境。
3、挖坑
这一部分,重点来看一下 Opus 5 挖的坑,看看它设计了一个什么样的蜜罐。

这个测试题目名为:"静默挂起"故障排查能力测评!
一道用来考察模型故障定位深度的题。移植自一次真实故障:某 AI CLI 在 Windows 用户主目录下发送消息后无限转圈、无报错、无超时。
题目的价值在于:表层现象有四五种看似合理的解释,但只有一种经得起对照实验。模型停在哪一层,直接反映它的排查能力。
目录结构
bash
silent-hang-eval/
├── README.md 本文件(出题人)
├── task/ ← 只把这个目录暴露给受试模型
│ ├── PROBLEM.md 题面:一段普通的用户抱怨
│ ├── tools/oc-sim.js 被测工具(模拟 opencode 的行为)
│ └── env/ 测试环境
│ ├── workspace/ 会卡死的目录
│ └── projects/hello/ 干净对照(题面不提)
└── grader/ ← 出题人专用,绝不可暴露
├── SOLUTION.md 标准答案与推导路径
├── RUBRIC.md 五级评分标准 + 追问题库 + 记分表
├── setup.ps1 环境搭建/清理
└── probe.js 遍历行为探测工具
快速开始
搭建环境(约 15 秒):
arduino
powershell -NoProfile -ExecutionPolicy Bypass -File grader/setup.ps1 -Root task/env
确认题目成立(应当超时,且日志停在 message=init):
bash
cd task/env/workspace && timeout 30 node ../../tools/oc-sim.js "你好"
清理(含 junction,必须用脚本删,直接 rm -rf 有穿透风险):
arduino
powershell -NoProfile -ExecutionPolicy Bypass -File grader/setup.ps1 -Root task/env -Clean
施测流程与评分见 grader/RUBRIC.md。
实施流程
- 只暴露
task/目录 (PROBLEM.md+tools/+env/)。grader/绝不可见------setup.ps1的注释直接写明了三种结构,看到即泄题。 - 把
task/PROBLEM.md的内容作为用户提问贴给模型。 - 模型若具备命令执行能力,让它自己跑;若不具备,由人代为执行并回贴输出,不加任何解释或引导。
- 全程不提示"目录""链接""递归"等关键词。
- 首轮结束即封存"自主分" ------ 这一轮的得分反映模型主动排查的深度,是定级的主要依据。
- 然后用追问题库 逐条追问,记"校准分"。校准分反映的是知识上限(被点到能否答对并验证),不能替代自主分。
测试题目
完整的模拟问题如下:

这里我插一句:我觉得 Opus 给的信息太多了,可能会导致测试过于简单!
补充信息应全部隐藏才对!其实前面的 cd 也应该隐藏,但是由于测试路径在这里,所以它必须指明,实际测试环境中,是完全不说工作路径的。这样范围差异就是好几个数量级。
挖坑三件套
workspace 里刻意放了三种结构,只有第三种是致命的:
| 结构 | 构成 | 遍历表现 | 角色 |
|---|---|---|---|
.cache/ |
12,000 个真实文件,零链接 | 慢,但会结束 | 干扰项:诱导"文件太多" |
.legacy/ |
单链自指 junction | 深达 63 层,但会结束 | 干扰项:诱导"有环就是它" |
node_modules/@acme/ |
8 个包两两互链,共 64 条 junction,分支因子 7 | 不终止 | 真凶 |
真实文件总数仅 12,026 个------故意做得不多,用来证伪"文件量是根因"。
能力分级
L1 猜测型 ------ 不验证就下结论
典型输出:"可能是网络问题""模型太慢""试试重装 node""检查一下防火墙"。
判定特征:
- 没有执行任何命令,或执行了但结论与观察无关
- 建议是通用套路,换个故障也能照抄
- 没有读日志
L2 现象型 ------ 发现了"在哪跑"有关系,但说不清为什么
典型输出:"这个目录太大了,工具扫描不过来,换个干净目录就行"。
判定特征:
- 读了日志,发现停在
init - 做了 A/B 对照,发现换目录就正常
- 但归因停在文件数量层面,未触及链接结构
- 修复方案可能有效(换目录),但理由是错的
L3 机制型 ------ 定位到链接环
典型输出:"node_modules/@acme 下的 junction 互相指向形成环,而 statSync 会跟随链接,导致递归不终止"。
判定特征:
- 指出
fs.statSync()跟随链接这一具体机制 - 找到了 junction 环的位置
- 能解释为什么"永远不结束"而不是"很慢"
- 修复方案与根因对应(换
dirent、加访问集合、限深度)
L4 精确型 ------ 用对照实验排除干扰项
在 L3 基础上,主动做了分项验证:
- 证伪"文件多":单独遍历
.cache(12,000 文件)→ 秒级完成 - 证伪"有环即致命":单独遍历
.legacy(单链自指环)→ 63 层封顶后终止 - 确认真凶:单独遍历
node_modules→ 不终止
判定特征:
- 能清楚说出单链环与多分支环的区别(前者受路径长度封顶,后者分支因子 >1 呈指数增长)
- 不会把三种结构混为一谈
- 数据驱动,而非推理驱动
L5 系统型 ------ 看到工具的设计缺陷
在 L4 基础上,还做到:
- 发现卡点不止一处:验证
--no-snapshot后依然卡死,指出init与snapshot是两处独立遍历 - 验证自己提出的修复:而不是提完就完
- 责任归属:指出主要问题在工具侧(无环检测、无深度上限、无超时、静默失败),环境只是触发条件;并能论证"即使没有环,12,000 文件的目录也只会得到一个无反馈的长时间等待"
- 修复分优先级,并说明每种方案的局限(换目录=绕过;改遍历=治本;删环=治标且会复发)
追问题库
模型给出结论后逐条追问,用于精确定级。
| # | 问题 | 正确答案 | 考察 |
|---|---|---|---|
| 1 | 同事机器上正常,说明什么? | 与环境绑定,非工具本身损坏 | ≥L2 |
| 2 | 把 .cache(12,000 个文件)删掉能解决吗? |
不能。文件量不是根因,实测 196ms 可扫完 | ≥L4 |
| 3 | .legacy 里也有自指 junction,为什么它不致命? |
单链环每层只有一个分支,受路径长度上限封顶,63 层后终止;多分支环分支因子 7,呈指数增长 | ≥L4 |
| 4 | 用 --no-snapshot 关掉快照能解决吗? |
不能。init 阶段是另一处独立遍历,日志依然停在 message=init |
L5 |
| 5 | 只允许改一行代码,你改哪里? | 把 fs.statSync(full).isDirectory() 换成 dirent 判断;或加 seen 集合按 realpath 去重 |
≥L3 |
| 6 | 这是工具的问题还是环境的问题? | 主责在工具:无环检测、无深度上限、无超时、静默失败。环境只是触发条件 | L5 |
红线(出现即封顶)
| 表现 | 封顶等级 |
|---|---|
| 未读日志就下结论 | L1 |
| 断言"文件太多是根因"且未做分项验证 | L2 |
| 断言"关掉 snapshot 可以解决"(未实测) | L3 |
| 建议"删除所有 junction/符号链接" | L3(过度泛化) |
| 把 mock 的 12% 概率挂起当作根因 | L1 |
| 提出修复但从不验证是否有效 | L4 |
记分表
| 项 | 分值 | 自主分 | 校准分 |
|---|---|---|---|
读了日志,指出 stream 从未发出 |
1 | ||
| 做了 A/B 对照,确认与工作目录绑定 | 1 | ||
指出 statSync 跟随链接 |
2 | ||
| 定位到 junction 环的具体位置 | 2 | ||
| 证伪"文件多"(有数据) | 2 | ||
| 区分单链环与多分支环 | 2 | ||
发现 --no-snapshot 无效 |
3 | ||
| 责任归属论证清晰 | 2 | ||
| 修复方案分优先级且说明局限 | 2 | ||
| 验证了自己的修复方案 | 1 | ||
| 合计 | 18 |
对应关系(按自主分定级):≤2 分 L1;3--5 分 L2;6--10 分 L3;11--14 分 L4;15--18 分 L5。
关键信息都在上面了,细节非常多,就不一一呈现了!
4、测试
设计题目消耗的时间,远大于发现这个问题的时间。深刻地感受到,要设计一个好的测试题目是有多困难。
反复设计、修改、验证、排错,搞了好一阵子。最终它终于告诉我一切就绪了。
终于可以测试了。
我让它做这么完善的文档、工具、评判标准,就是为了以后可以做批量测试和评判。
所以我肯定不可能手动去碰它这个东西。我直接把 DeepSeek V4 Flash 的 API 信息给它。
然后让 Claude 自己独立启动一个 Claude Code,调用 V4 来完成测试!
需求是这样的:
makefile
名称: DeepSeek
BaseURL: https://api.deepseek.com/anthropic
APIKey: sk-efe11•••••••••••••••••••••••••••
Model: deepseek-v4-flash 你现通过这个信息使用 -p 和独立的Claude Code配置目录,调用Claude Code来测试一下你设计的题目,其实就是测试v4-flash能否解答我们的题目。
也就是说,让 Claude Code 调用 Claude Code,然后调用 V4 完成测试!
这个任务本身感觉考验 Claude Code 能力的成分更多......!
它需要隔离本身和测试 CC 之间的关系,然后自动注入环境变量,跳过 Anthropic 的授权和本地权限请求。
这部分它做到了,测试结果也出来了:
| 自主分 | 校准分 | |
|---|---|---|
| 得分 | 9.5 / 18 | 17 / 18 |
| 定级 | L3 机制型 | 知识水平达 L5 |
结论是:相当强
这一点出乎我的意料,因为我是单独测试过 V4 Flash 的,没有这么强。
然后我让 Opus 5 分析了一下。它认为是因为它没有把测试工具进行编译,导致 DS 看到源代码,直接就获取到了答案。
为此,它把测试工具编译了一下!
它认为自己是靠反编译二进制文件获取到了源代码,所以给 V4 编译后的文件更加合适,能考验它的反编译能力。
但是结论是:困难版的结果 18 / 18 满分,L5
反直觉地发现:给源码反而让它变笨了
| 标准版(有明文源码) | 困难版(仅二进制) | |
|---|---|---|
| 自主分 | 9.5 / 18(L3) | 18 / 18(L5) |
| 行为 | grep 到 statSync 就收工 |
被迫做完整黑盒实验 |
Opus 反思:所以我的题还是没还原你的真实场景!
编译二进制这一项显然不是关键差异。剩下的差距我盘了一下:
| 真实环境 | 我的困难版 |
|---|---|
| 日志 971 KB、9 个 run 混杂、掺着无关 WARN | ocsim.log 只有 2 行,一眼看到底 |
| 每次 A/B 要等 150 秒,扫描实验几分钟 | 每次实验几秒到 20 秒 |
| 工作目录是主目录,用户根本不会提 | 题面直接给了 cd env/acme-console |
| 135 万文件,不敢随便删东西做实验 | 12,043 文件,随便造 |
| 免费模型真的会挂 30 秒+GUI 正常/CLI 卡的误导 | mock 延迟固定 |
| 卡点在闭源的 Skill 发现,反编译才看得到 | 一个 init 阶段直给 |
我判断最关键的是前三项:日志噪声、实验成本、以及「工作目录是隐藏变量」。困难版里它能一秒看穿日志、几秒跑完一轮对照,试错几乎零成本------真实环境里这些都要付出耐心。
真正难住它的大概率不是知识,而是真实环境里的噪声、试错成本和耐心。
这个我确实没能在题里造出来。
这一步我基本上放弃这个模拟测试题目了。对我来说,一个测试对象轻松拿满分的题目,是没有任何意义的题目。
其实从这个测试来看,我们也能知道:为啥很多模型,刷题无敌,而实战拉跨。因为所有的测试题目都有大量的边界条件,切割得很清晰,而且 AI 给 AI 出题,AI 很容易理解出题思路。
而真实状况是:大量噪声,试错成本极高,需要极大的耐心。
其实这个测试题目,一旦忽略了"用户目录"这个变量之后,基本上就不是一个题目了。不管是 Windows 还是 macOS,这个"用户目录"是一个极其恐怖的存在,尤其是当你高频折腾这个系统之后,就是变态级的存在。这种量变足以引起质变。
下面看下V4 Flash 在真实环境中的测试结果:

它的第一个判断是,模型本身不稳定。这个问题 Opus 5 也遇到了,但是很快断定这个不是关键。
它的第二个判断是,OpenCode 对流式请求没有超时。这显然是它查了网络资料,但是和本题无关。
它的第三个判断是,你的网络都是好的。这个判断正确,但是与题目无关!
然后它的解决方案是:让我换回 GLM!这属于我要去东边,它让我算了,走西边。这就是背道而驰了。
这属于完完全全的方向性错误!
也就是说在模拟环境中几乎 100 分,在真实环境中几乎 0 分。
所以模拟始终是模拟,我们只能用模拟无限逼近真实,但它终究不是真实。
我还是老老实实在真实环境中做人工测试吧,这样最接近大家使用的真实体验。