本地测试通过、CI 却失败时,先给代理一份环境对照

使用 AI 辅助编写与复核。

本文目录

本地检查成功、CI 失败时,先把两次运行的对照交给编程代理,再要求修复。从相同的检出提交和测试选择开始,比较工作目录、运行时、依赖状态、输入与工作流事件,再根据差异选择一个能被聚焦检查推翻的假设。

GitHub 的工作流排查指南将运行日志、触发条件、执行条件和运行器列为调查入口。下面给出一种原创整理方法,把这些信息组织成代理请求;它不代表已经确定某次真实 CI 失败的原因。

把记录绑定到同一份代码

下面的项目、配置、路径、运行记录和观察均为虚构。记录是编写的示例,不是本文捕获的日志或实际运行的命令。EXAMPLE-C17 是示意提交标签,不是真实 Git 对象 ID。

假设仓库中有两份模式样例文件:

shop/
  fixtures/
    export-schema.json
  packages/
    exporter/
      fixtures/
        export-schema.json
      tests/
        test_schema.py
  ci/
    task-description.txt

根目录的样例是较早的一份:

{"id_field": "orderId"}

packages/exporter/ 内的样例是:

{"id_field": "order_id"}

packages/exporter/tests/test_schema.py 的相关部分是:

import json
from pathlib import Path
import unittest


class SchemaTests(unittest.TestCase):
    def test_export_id_field(self):
        schema = json.loads(
            Path("fixtures/export-schema.json").read_text(encoding="utf-8")
        )
        self.assertEqual(schema["id_field"], "order_id")


if __name__ == "__main__":
    unittest.main()

断言表达了应用要求的字段名,但不能确定某次运行实际打开了哪份样例文件。代码使用相对路径,因此运行时工作目录与这个问题有关。测试语法及 assertEqual 的含义可参阅 Python 3.13 unittest 文档。

与具体 CI 提供方无关的任务描述如下,保留示例配置的原文:

checkout: requested revision
restore cache: workspace-fixtures
working directory: checkout root
runtime: Python 3.13.2
command: python "$CHECKOUT/packages/exporter/tests/test_schema.py"

这是一份虚构任务描述,不是可直接部署的 GitHub Actions 工作流。缓存步骤有意没有写完整:它没有说明恢复了哪些文件。

写清楚实际记录了什么

虚构的本地记录写道:

Revision label: EXAMPLE-C17
CHECKOUT: /work/shop
Working directory: /work/shop/packages/exporter
Runtime reported: Python 3.13.2
Command: python "$CHECKOUT/packages/exporter/tests/test_schema.py"
Selected test: SchemaTests.test_export_id_field
Reported outcome: success
Fixture path and content captured during run: not recorded
Working-tree changes captured during run: not recorded

虚构的 CI 记录写道:

Checked-out revision label: EXAMPLE-C17
CHECKOUT: /work/shop
Working directory: /work/shop
Runtime reported: Python 3.13.2
Command: python "$CHECKOUT/packages/exporter/tests/test_schema.py"
Selected test: SchemaTests.test_export_id_field
Reported outcome: assertion mismatch, actual orderId, expected order_id
Event: pull request
Fixture path and content captured during run: not recorded
Post-cache working-tree changes captured during run: not recorded

这些记录提供了一条具体线索:工作目录不同,两份所给样例的内容也不同。但它们不能证明任何一次运行中实际发生了什么;缓存或其它准备步骤可能在检出后替换了样例文件。

填写环境差异表

维度 本地记录 CI 记录 下一步要确定什么
检出修订 EXAMPLE-C17 EXAMPLE-C17 真实场景中记录完整检出提交 ID,包括 CI 是否测试了合并修订
工作目录 packages/exporter 仓库根目录 在失败步骤当下记录目录,不用更早的准备步骤代替
命令与选择 同一脚本和测试 同一脚本和测试 确认封装脚本没有改变参数或测试发现方式
运行时 报告为 Python 3.13.2 报告为 Python 3.13.2 仍怀疑解释器选择时,记录可执行文件路径
依赖 脚本使用上面所示标准库导入 声明为同一脚本 提议升级依赖前,先查实际导入与配置
选中的样例 未记录 未记录 记录解析后的样例路径及不含敏感信息的内容
缓存与准备 本地准备未记录 恢复了样例缓存 查看恢复路径,在恢复后比较样例内容
事件与修订处理 手工调用 pull request 事件 联合查看事件、检出和条件化准备过程
相关环境 已记录 CHECKOUT 已记录 CHECKOUT 只补充某个假设需要的特定变量
执行时工作区 未记录 缓存恢复后未记录 确定未跟踪文件或已改文件是否影响运行

相同修订只是起点。生成文件、恢复的产物、环境变量和命令封装,仍可能让真正使用的输入不同。检查这些差异前,不应把两次运行描述为等价。

至少区分两种原因

假设 A:工作目录选中了错误的样例文件。 按所示代码,从根目录运行时会寻找根目录样例;从 exporter 目录运行时会寻找 exporter 内的样例。所给例子的字段名与记录中的不匹配现象相符。

验证 A,可以在失败环境读取样例时,立即记录解析后的路径和字段值,或采用等价的小范围诊断。如果失败运行实际打开的是 exporter 样例,而且该文件仍包含 order_id,那么 A 就不能解释这份记录。不要只是重复最初的对照表,就说已经“确认”了 A。

假设 B:恢复或生成的内容与受版本控制的样例不同。 两次运行可以检出同一提交,而 CI 的样例缓存随后提供了不同字节。

验证 B,可以在可丢弃的诊断运行中,查看缓存覆盖的路径范围,并比较准备步骤前后的实际样例内容。如果相关内容没有改变、也与受版本控制的样例一致,这个具体的缓存内容假设就缺少支持。命中缓存本身不等于缓存损坏。

先从 A 开始,因为现有记录明确给出目录差异,而聚焦的路径观察能直接区分两份已知样例。如果观察与 A 矛盾,再调查 B。同一次尝试中大幅修改运行时、缓存和测试,会让结果更难解释。

复制这份调查请求

调查同一检出修订上的 SchemaTests.test_export_id_field,
为什么本地记录成功、CI 记录失败。

所给证据:
上面的目录树、两份模式样例、测试片段、与提供方无关的任务描述,
以及成对记录。这些就是当前证据;根因仍为 UNKNOWN。

先确定实际检出提交、测试选择、测试步骤当下的工作目录、
运行时可执行文件,以及相关准备/缓存条件。
不要把本地分支顶端与尚未核实的 CI 合并修订作比较。

第一个假设:
相对样例路径在 CI 中选中了根目录样例。
提出一项诊断,在失败步骤记录解析后的样例路径和相关字段。
说明什么观察能够推翻这个假设。

第二个假设:
缓存或生成过程在检出后改变了样例内容。
如果第一个假设不成立,查看相关路径和实际内容。

先读源码和已有日志。确定要做哪项诊断前,不重跑工作流、
不修改配置,也不编辑测试。
不要输出全部环境变量值或任何秘密信息。
证据只保留指定路径、字段、运行时、修订和相关准备条件。

不要删除断言、把 order_id 改成无法解释的实际值、禁用检查、
扩大权限,或拿升级依赖当试探性修复。

返回对照表、支持和反对各假设的证据、最小下一项检查,
以及仅在有支持时提出的小范围改动方案。
区分已有运行记录、手工预测和新的执行。

让观察结果决定怎样修复

后续证据如果确认仓库原本要使用 exporter 样例,而路径从错误目录解析,就可以考虑显式指定预期路径,或调整配置的工作目录。具体选择应遵循仓库测试约定和任务范围,之后仍需确认检查继续约束 order_id。

如果是缓存恢复提供了错误内容,就调查恢复范围及产物身份。修改预期断言,不能解释输入为何出错。

本练习交付的是调查请求和两个可验证假设。用于区分原因的检查没有在本文执行,因此实际根因仍然未知。处理自己的场景时,应先把检查结果补进对照,再请代理实施修复。

在 CueSpec 会话中,带着新的日志片段回复前,可先阅读最新回答,让下一条请求对应当前假设。