用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会员订阅渠道,有需要可自取。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐