ChatGPT、Codex实战排查:本地Build和Test都通过,为什么提交到CI以后还是失败?
用Codex改项目时,一个很容易让人怀疑人生的问题是:
本地明明全部正常,为什么一提交到CI就红?
最典型的场景是:
你让Codex修一个Bug。
它修改代码以后,在本地依次执行:
Build通过。
Unit Test通过。
Lint也通过。
你自己再跑一遍,还是正常。
于是提交代码。
结果CI开始运行以后:
Build Failed。
Test Failed。
甚至出现一个本地从来没见过的错误。
这时候最容易产生两个判断:
“是不是CI坏了?”
或者:
“Codex其实根本没修好?”
都不一定。
很多时候真正的问题是:
本地和CI运行的,看起来是同一份代码,实际上却不是同一个执行环境。
所以这类问题最核心的一句话是:
Local Pass ≠ CI Pass
本地通过,只能证明代码在当前本地状态里成立。
它并不能自动证明:
代码在一个干净、独立、重新初始化的CI环境里仍然成立。
一、先别急着改代码:CI失败最先查的其实不是Bug
假设Codex本地运行:
npm run build
npm test
全部通过。
CI却在同样命令上失败。
很多人第一反应是:
重新让Agent改实现。
这往往是错误顺序。
因为如果真正原因是:
Node版本不同。
依赖版本不同。
环境变量缺失。
缓存状态不同。
测试数据库不同。
那么继续修改业务代码,只会把一个环境问题越改越复杂。
所以遇到:
本地Pass,CI Fail
第一步不要问:
“代码哪里错了?”
而应该先问:
CI和本地,到底有哪些状态不一样?
二、第一类高频原因:Runtime版本根本不一样
这是最值得先排查的一类。
比如你本地使用:
Node 22。
但CI配置还在:
Node 20。
或者本地Python版本是:
3.13。
CI仍然使用:
3.11。
代码本身可能没有问题。
但不同Runtime版本可能影响:
语法支持。
依赖兼容。
模块解析。
类型行为。
默认配置。
于是本地完全正常,CI直接Build失败。
怎么查?
不要只看自己电脑上:
node -v
python --version
java -version
还要确认CI Workflow里真正设置的Runtime。
例如GitHub Actions里的:
setup-node
setup-python
或者Docker基础镜像。
真正要做到的是:
Runtime Parity
本地和CI使用同一代Runtime。
否则你验证的根本不是同一个执行条件。
三、第二类原因:本地用的是“已经装好的依赖”,CI却从零安装
这个问题非常常见。
本地开发环境可能已经用了很久。
node_modules
Python虚拟环境
Gradle缓存
Maven缓存
里面都存在之前安装好的依赖。
Codex修改以后,你直接跑测试。
正常。
但CI通常会从一个相对干净的环境重新安装。
这时真正决定结果的是:
package-lock.json
pnpm-lock.yaml
poetry.lock
requirements.txt
等版本记录。
如果Codex:
改了依赖。
但没更新Lockfile。
或者你本地装了某个版本,却没有真正写进Repository。
本地当然能跑。
CI重新安装时拿不到同样状态。
于是失败。
最简单的验证办法
不要只在旧环境里继续运行。
本地做一次:
Clean Install
例如清掉依赖以后,完全按照CI的安装方式重新安装。
如果这时候本地也失败了,
说明问题不是CI。
而是:
原来的本地环境掩盖了依赖问题。
四、第三类原因:环境变量在本地存在,CI里没有
这也是Codex任务特别容易踩的坑。
比如项目依赖:
数据库URL。
API Key。
Feature Flag。
第三方服务地址。
构建参数。
本地.env里已经配置好了。
所以Build、Test全部正常。
但.env通常不会提交Git。
CI如果没有对应Secret或者Environment配置,就可能:
启动失败。
测试失败。
甚至直接在Build阶段报错。
更麻烦的是,有些代码对缺失ENV有默认行为。
于是本地和CI不是“一个报错,一个不报错”。
而是:
走了不同的业务路径。
排查时别只问“ENV有没有”
还要确认:
变量名称是否一致。
格式是否一致。
是否存在空字符串。
测试环境和生产环境是否读取不同配置。
不要让Codex看到一个CI错误以后马上改逻辑。
先确认:
CI是不是拿到了它应该拿到的配置。
五、第四类原因:本地存在没有进入Git的文件
和Worktree问题很像。
Codex可能在本地生成了:
配置文件。
Type文件。
客户端代码。
构建产物。
测试Fixture。
然后本地Build通过。
但这些文件如果:
没有提交。
或者被.gitignore忽略,
CI Checkout以后根本不存在。
于是本地成功依赖的是:
Workspace State
而CI拿到的只有:
Repository State
这是两件不同的东西。
怎么快速确认?
提交前先看:
git status
再检查项目有没有:
Codegen。
Schema Generation。
Build Preparation。
资源生成步骤。
真正稳定的工程流程应该做到:
一个全新Checkout也可以通过标准命令自动重建这些文件。
六、第五类原因:缓存让本地“看起来是好的”
本地长期开发以后,可能积累:
编译缓存。
Test Cache。
Framework Cache。
Gradle Cache。
构建产物。
旧生成文件。
这些状态有时候会让一个本应失败的问题暂时不出现。
CI却是相对干净环境。
它没有这些缓存。
于是反而暴露真实问题。
所以遇到:
本地绿,CI红,
可以做一个很重要的动作:
Clean Build
删除:
Build目录。
测试缓存。
生成产物。
必要的工具缓存。
然后重新执行和CI一样的完整流程。
如果Clean以后本地也失败,
说明CI其实是在帮你发现:
本地缓存掩盖的问题。
七、第六类原因:测试本身存在顺序依赖或共享状态
这类问题更隐蔽。
你本地可能经常只运行:
某个测试文件。
某个Module。
失败用例附近的测试。
所以全部通过。
但CI通常会执行:
Full Test Suite。
于是不同测试之间的隐式依赖暴露出来。
例如:
测试A写数据库但没有清理。
测试B默认数据库为空。
单独跑:
A通过。
B也通过。
一起跑:
B失败。
或者:
某个测试修改全局Mock。
没有恢复。
后面的测试受到影响。
这就是:
Test Isolation Problem
测试隔离问题。
所以看到CI里“无关测试”失败时,不要立即认为CI环境有问题。
要检查:
单独跑是否通过?
整组跑是否失败?
改变顺序以后结果是否变化?
如果答案是:
单独通过,整组失败,
就高度怀疑共享状态或顺序依赖。
八、第七类原因:CI比本地执行得更严格
很多项目本地开发时只跑:
部分Test。
快速Build。
开发模式检查。
CI却可能多跑:
Type Check。
Lint。
完整Regression。
Integration Test。
Security Check。
严格Warning策略。
比如本地:
Warning只是Warning。
CI里可能配置成:
Warning as Error。
于是开发者感觉:
“我本地都Build了,为什么CI Build还失败?”
实际上两边执行的根本不是完全同一套Command。
所以排查时很重要的一步是:
把CI真正执行的命令原样拿回本地执行。
不要自己猜一个“差不多”的命令。
九、最有效的排查顺序:按环境差异查,不要乱改
如果遇到:
Local Build/Test Pass → CI Fail
我建议按这个顺序排。
第一步:确认失败发生在哪一层
先区分:
Install失败?
Build失败?
Test失败?
Lint失败?
Integration失败?
不要看到整个Pipeline红了就直接让Codex“修CI”。
第二步:对齐Runtime
确认:
Node。
Python。
Java。
系统环境。
容器镜像。
和本地是否一致。
第三步:检查依赖和Lockfile
做一次干净安装。
验证Repository能否重新建立依赖。
第四步:检查环境变量
对比CI需要的ENV和Secrets。
不要泄露Secret值,只确认是否存在、名称和用途是否一致。
第五步:清理本地状态
删除缓存、Build产物、生成文件。
重新开始。
第六步:执行CI原始命令
CI跑什么,本地就跑什么。
包括参数和顺序。
第七步:最后才分析代码问题
如果以上环境都已经一致,CI仍然稳定失败,
这时候再让Codex深入看:
并发。
测试隔离。
平台差异。
真实逻辑Bug。
这样效率会高很多。
十、一个很实用的自测指标:CI Reproduction Rate
孤狼这种故障排查文章,一个指标就够:
CI Reproduction Rate
简单理解:
CI出现的问题,有多少可以在一个干净的本地环境里稳定复现?
如果CI失败以后,你按同样Runtime、同样依赖、同样命令,在本地Clean环境里很快就能复现,
说明工程环境的一致性比较好。
这类问题通常比较容易排查。
但如果经常出现:
CI红。
本地怎么都不红。
换电脑又是另一种结果。
那说明你的:
Environment Parity
环境一致性
还比较差。
这时候真正应该先解决的是:
可复现性。
而不是继续让Agent扩大修改范围。
十一、怎么让Codex以后更容易排这类问题?
第一,不要只告诉它:
CI失败了,帮我修。
应该同时提供:
CI失败阶段。
关键错误日志。
本地成功命令。
Runtime版本。
这样它才不会直接从业务代码开始猜。
第二,可以先让Codex做:
Difference Analysis
先列出本地与CI可能存在的环境差异,
再决定修改什么。
第三,优先让Agent复现CI。
如果一个Bug都无法本地复现,就直接改代码,容易进入:
Guess → Modify → Push → Wait CI → Fail Again
这种非常低效的循环。
十二、为什么这类问题很容易浪费Plus额度?
CI故障特别容易形成一种循环:
Codex修改。
Push。
等CI。
失败。
再看日志。
再修改。
再Push。
如果每次都没有新增Evidence,只是在根据最后一条错误盲改,就会产生大量低价值Retry。
所以真正应该优化的是:
一次CI失败后,能不能先把它变成一个本地可复现问题。
一旦能复现,
Agent就可以快速:
修改。
测试。
重新验证。
而不是每次都依赖远程CI充当调试器。
十三、什么时候Plus通常已经够?
如果你现在经常遇到:
本地和CI版本不一致。
依赖状态混乱。
ENV缺失。
Clean Build无法稳定复现。
测试有顺序依赖。
那么Plus通常已经够你处理这些问题。
因为真正瓶颈不是:
AI执行容量。
而是:
工程环境没有标准化。
先把:
Runtime固定。
依赖锁定。
CI命令本地化。
环境配置显式化。
Clean Build流程。
做好。
同样的Codex容量往往就能少掉大量无效Retry。
十四、什么时候Pro才真正开始匹配?
如果你的工程已经做到:
本地与CI Runtime高度一致。
依赖能稳定重建。
CI命令可以本地复现。
环境配置标准化。
CI Reproduction Rate很高。
大部分失败都可以快速定位。
但每天仍然存在大量:
复杂Build。
大型Test Suite。
多项目CI。
长时间调试。
多个高价值工程任务并行。
并且这些有效任务持续受到Codex使用容量限制,
这时候问题才真正从:
Environment Problem
环境问题
变成:
Capacity Problem
容量问题。
这时Pro的更高容量才更容易真正转化成:
更多有效工程产出。
最后
本地Build和Test全部通过,提交到CI以后却失败,并不一定意味着:
代码突然坏了。
很多时候真正发生的是:
本地验证了一个已经运行很久的Workspace。
CI验证的却是:
一个从Repository重新创建出来的干净环境。
所以遇到这类问题时,最重要的不是立即再改一版代码。
而是先确认:
本地和CI,到底是不是在运行同一个世界?
Runtime。
依赖。
ENV。
缓存。
生成文件。
测试状态。
执行命令。
只要其中一个不一样,Local Pass就不能直接推导出CI Pass。
真正成熟的工程流程应该做到:
CI能做的验证,本地也尽量能够用同样条件复现。
当这个条件建立起来以后,Codex才不会一直陷入:
改代码 → Push → 等CI → 再失败
这种昂贵循环。
所以以后看到:
Local Pass,CI Fail
先别急着怀疑代码。
先检查:
Environment Parity
这通常才是最快找到真正问题的起点。
持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了稳定的Plus/Pro会员订阅渠道,有需要可自取。
更多推荐



所有评论(0)