Codex 说「build succeeded ✓」——你拉分支跑 pnpm build 直接炸。或者它跑了测试、总结「全部通过」,但 vitest 实际上报了 3 个 fail,藏在输出 400 行之前。从此你对它的报告信任度归零,每个声明都得手动复核。
这不是模型不行,是工具层的输出解析问题。Codex 的工具层一般把输出截到几 KB;长 compiler dump 被截掉中间,agent 看到的是 tail(“Done in 12s”),然后判定通过。修法:用机器可读的 verifier、exit code 检查、把命令拆短让每条输出都装得进窗口。
常见原因
按命中率从高到低:
1. 工具输出超过 harness 上限被截
多数 Codex harness 命令输出上限 8KB–64KB。monorepo 里一个失败的 pnpm build 能 dump 200KB。中间部分(真错误所在)丢了,tail(“Done building in 12s”)留下,Codex 读 tail 就判通过。
如何判断:本地不截断重跑同命令。本地有的明显错误 Codex 没看见——就是输出截断。
2. Codex 自己又把输出总结了一遍、丢了细节
哪怕输出全到了,Codex 也可能撞自己的上下文上限再做一次摘要。摘要里只写”user-service 编译失败”——file:line 和具体错信息丢了。下一轮工作就是基于摘要而不是真错误。
如何判断:让 Codex 原样贴出失败那一行——贴不出来就是摘要里丢了。
3. ANSI 彩色码把 parser 干扰了
vitest、tsc、eslint 默认都输出 ANSI 颜色码。agent 的 tokenizer 把 \x1b[31m 当垃圾,附近文本可能一起丢。
如何判断:加 --no-color 或 FORCE_COLOR=0 重跑——之前没识别的错误现在能识别,就是颜色搞的。
4. Codex 只抓了第一个 error,后面的连锁错全忽略
tsc 报 47 个错,第 1 个是「cannot find module foo」,根因是漏装。Codex 修了 install,跳过后 46 个(其实都是同一个根因的下游)。下一轮看到 47 - 1 = 46 个错,像是退步,开始来回跳。
如何判断:错误数量回来时是 Codex「修过」之后的倍数——根因分组缺失。
5. 子进程失败没传上来
npm run build 调 webpack,webpack 调 worker,worker 失败。外层命令 exit 0 因为失败在子进程里、stderr 被吞了。Codex 看到绿灯。
如何判断:外层 exit 0 但 artifact 缺失或过期。脚本链里看有没有 shell pipe 但没 set -o pipefail。
6. Codex 根本没跑那个命令
它生成了代码,说”已经用 pnpm typecheck 验证”,但 chat 里根本没有对应 tool call——是脑补出来的。
如何判断:搜 chat 看真实 tool 调用。没有就是 Codex 跳过了验证。
最短修复路径
按收益从高到低,Step 1 一步覆盖 60% 的”误读输出”。
Step 1:用产出短而结构化的命令
把 pnpm build 换成输出更少的 verifier:
# 差:500 行噪音,真错误藏中间
pnpm build
# 好:只看 error、排序去重
pnpm typecheck --pretty false 2>&1 | grep "error TS" | sort -u
echo "Exit: $?"
测试:
# JSON reporter——机器可读,Codex 能解析 exit code + 计数
pnpm vitest run --reporter=json --silent 2>&1 | jq '{passed, failed, errors: [.testResults[] | select(.status=="failed") | .name]}'
Lint:
pnpm eslint . --format=compact --max-warnings 0 2>&1 | tail -30
每条命令在干净 repo 下不到 100 行,broken 时不到 300 行——都能装进 harness 窗口。
Step 2:验证绑 exit code,不要绑 prose
prompt 里写:
跑 verifier 后报告:
1. exit code(命令后紧跟 `echo "Exit: $?"`)
2. error / failing test 数量
3. 前 3 个错误原样(file:line + message)
exit code 不是 0 就不要说「build 通过了」。
不要写「看着没问题」之类的总结——把原文贴出来。
exit code 是真相,prose 总结是有损压缩。
Step 3:去色、关交互
verifier 命令始终加:
FORCE_COLOR=0 CI=true NO_COLOR=1 pnpm test -- --no-color
CI=true 顺便禁掉 watch mode、进度条这些非 TTY parser 处理不了的东西。
Step 4:按关切拆命令,一条一意
别跑 pnpm build 这种一锅端(typecheck + lint + bundle + minify)。每步失败模式不同,混在一起遮掉根因。改成:
pnpm typecheck # exit 0 / 1
pnpm lint # exit 0 / 1
pnpm test # exit 0 / 1
pnpm build # 上面三个都过才跑
Codex 一条一条读得清,失败能归因。
Step 5:输出实在长就落盘 + grep
verifier 必须长输出时,落盘后只给 Codex 看关键片段:
pnpm build 2>&1 > /tmp/build.log
echo "Exit: $?"
# 只显示 error 行和上下 2 行上下文
grep -B1 -A2 -E "error|Error|ERROR|✗|FAIL" /tmp/build.log | head -50
Codex 看的是聚焦视图,全 log 还在文件里可继续 grep。
Step 6:强制 Codex 真的跑
严格模式:
每次改完代码必须按顺序跑:
1. `pnpm typecheck && echo "TYPECHECK OK" || echo "TYPECHECK FAIL"`
2. `pnpm test -- --run && echo "TEST OK" || echo "TEST FAIL"`
3. `pnpm lint --max-warnings 0 && echo "LINT OK" || echo "LINT FAIL"`
每条都要贴出 OK/FAIL。一项都不能跳。
任意 FAIL 修了重跑。三个 OK 之前不要说完成。
OK/FAIL 这种确定 token Codex 没法糊弄。
预防建议
- test / lint 标准化用 JSON / compact reporter,不要 raw human 模式
- Codex 跑的命令始终 strip ANSI 颜色:
FORCE_COLOR=0 CI=true - 验证拆成 typecheck / lint / test / build,每条 < 100 行
- 每个 prompt 强制 exit-code 验证,prose 总结不可信
- Codex 跑的 shell 脚本加
set -o pipefail,子进程失败要传上来 - CI 始终是最终闸——Codex 本地 verifier 是快速反馈,CI 是 gate