1 分钟

Claude Code:CI 失败处理 — 小修复与回归测试的提示

Claude Code 用于 CI 失败:提示模型引用失败输出、建议最小修复并添加回归测试以防止重复发生。

Claude Code:CI 失败处理 — 小修复与回归测试的提示

CI 失败时 AI 猜测会出什么问题

CI 失败通常并不神秘。日志会告诉你停止的地方、哪个命令失败以及错误信息。好的日志包含栈追踪、带有文件和行号的编译器错误,或者显示哪个断言失败的测试报告。有时你还会看到类似“expected X, got Y”的 diff 式线索,或明确的失败步骤如 lintbuildmigrate database

真正的问题是人(和 AI)常把日志当背景噪音。如果你粘贴一大段日志然后问“修复方法”,很多模型会跳到熟悉的解释而不是读最后有意义的几行。当错误看起来常见(“module not found”、“timeout”、“permission denied”)时,猜测会更糟:你可能得到一次大改写、一个新依赖或“试着更新全部”的建议,而这些并不匹配真实失败。

目标不是“用某种办法让它通过”。更简单的目标是:

  • 读失败输出。
  • 找出让失败步骤通过的最小改动。
  • 其它都保持不变。

在实践中,“最小修复”通常是这些之一:单处几行的代码修改、缺失的 import 或错误的路径、对 CI 环境明显不合适的配置值,或者撤回一次意外的破坏性改动而不是重新设计代码。

后续的测试也很重要。CI 通过一次并不等于防止重现。如果失败来自某个边缘情况(空输入、时区、舍入、权限),请添加一个在修复前会失败、修复后会通过的回归测试。这把一次性的救火变成了一道护栏。

在求助前要收集什么信息

大多数糟糕修复都源于上下文缺失。如果你只粘贴最后一条红色行,模型就要猜测之前发生了什么,而猜测常变成改写。

目标是提供足够细节,让别人能从第一个真实错误一路看明白到结尾,然后尽量少改动。

尽量按原样粘贴以下内容(能逐字粘贴尽量逐字):

  • 从第一个错误行开始到结束的完整失败日志(不要只给最后的堆栈追踪)。
  • CI 运行的精确命令(例如 go test ./...npm testflutter testgolangci-lint run)。
  • 错误中提到的文件路径,以及任何相关配置(测试配置、lint 配置、构建脚本)。
  • 最近发生了什么变化:PR diff 摘要、依赖升级、CI 配置编辑。
  • 是否存在不稳定:如果有几次失败和一次通过,把它一并说明。

用简单的语言列出约束。如果你要一个很小的修复,要明确写出来:不重构、不改行为(除非必须)、把补丁限制在失败相关区域。

一个简单例子:CI 在 lint 步骤失败,恰好发生在一次依赖升级之后。粘贴从第一个警告开始的 lint 输出,包含 CI 使用的命令,并说明单个包版本变更。这通常足以建议一行配置调整或小范围代码修改,而不是格式化半个仓库。

如果你想要可以直接复制粘贴的结构,通常如下就够了:

CI command:

Failing output (full):

Recent changes:

Constraints (smallest fix, no refactor):

Flaky? (runs attached):

强制模型读取失败输出的提示规则

当模型在 CI 中未命中要点时,通常是因为你的提示允许它去猜测。你的任务是让它用确切的失败输出“展示其思路”,然后只落实最小改动以让任务通过。

保持模型诚实的规则

要求证据与一个小计划。一个好的提示会强制模型做五件事:

  • 引用 CI 日志中确切的失败行(错误、栈追踪、文件:行),并明确说明“我正在使用这些行”。
  • 给出一句话的诊断,不要含糊其辞。
  • 提出一个最小补丁计划,包含 1–3 处修改,明确列出将接触到的文件路径。
  • 避免无关改动(不要格式化、重命名、重构或升级依赖),除非你批准。
  • 列出不确定之处以及能确认诊断的单一额外信息。

不确定是可以接受的,但隐藏的不确定会浪费时间。

可复制粘贴的提示片段

把下面这段粘贴在你的 CI 提问顶部:

Use ONLY the evidence in the CI output below.
1) Quote the exact failing lines you are using.
2) Give ONE sentence: the most likely cause.
3) Propose the smallest fix: 1-3 edits, with file paths.
4) Do NOT do formatting/renames/refactors or "cleanup".
5) List uncertainties + the one extra detail that would confirm the diagnosis.

如果日志写着“expected 200, got 500”并伴随栈追踪到 user_service.go:142,这个结构会把响应推向该函数和一个小范围的守护或错误处理改动,而不是端点的重新设计。

一个用于 CI 失败的可复用提示模板

最快的办法是用一个强制引用日志、在约束内作答并在缺信息时停下的提示。

You are helping me fix a CI failure.

Repo context (short):
- Language/framework:
- Test/build command that failed: <PASTE THE EXACT COMMAND>
- CI environment (OS, Node/Go/Python versions, etc.):

Failing output (verbatim, include the first error and 20 lines above it):
<PASTE LOG>

Constraints:
- Propose the smallest possible code change that makes CI pass.
- Do NOT rewrite/refactor unrelated code.
- Do NOT touch files you do not need for the fix.
- If behavior changes, make it explicit and justify why it is correct.

Stop rule (no guessing):
- If the log is incomplete or you need more info (missing stack trace, config, versions, failing test name), STOP and ask only the minimum questions needed.

Your response format (follow exactly):
1) Evidence: Quote the exact log lines that matter.
2) Hypothesis: Explain the most likely cause in 2-4 sentences.
3) Smallest fix: Describe the minimal change and why it addresses the evidence.
4) Patch: Provide a unified diff.
5) Follow-up: Tell me the exact command(s) to rerun locally to confirm.

Then, write ONE regression test (or tweak an existing one) that would fail before this fix and pass after it, to prevent the same failure class.
- Keep the test focused. No broad test suites.
- If a test is not feasible, explain why and propose the next-best guardrail (lint rule, type check, assertion).

减少来回沟通的两个细节:

  • 包含确切的失败命令和第一个错误(不要只给最后的摘要)。
  • 如果有多个失败,说明优先修哪一个(通常是日志中最早的真实失败)。

如何推动最小修复而不是重写

用快照安全实验
尝试有风险的修复,如果让 CI 问题更糟可以立即回退。

最快丢失时间的方式是接受一个“清理”型补丁,它一次修改五处。事先定义“最小”:让失败的作业通过的最小 diff,风险最低且验证最快。

一个简单的规则很有效:先修症状,再决定是否值得做更广的重构。如果日志指向一个文件、一个函数、一个缺失的 import 或一个边缘情况,就把注意力放在那里。避免“顺手改”的修改。

如果确实需要备选方案,只问两种且仅两种:“最安全的最小修复” vs “最快的最小修复”。你想要的是权衡,而不是一大堆选项。

还要要求本地验证与 CI 匹配:让对方给出 CI 运行的相同命令(或最接近的等价命令),这样你能在几分钟内确认:

# 运行 CI 所运行的同一单元测试目标
make test
# 或 CI 使用的确切脚本
npm test

如果回复建议的是大改动,就逼问:“展示能修复失败断言的最小补丁,且不要包含无关的格式化或重命名。”

要求随修复一并给出的阻止复发的测试

没有测试的修复是在打赌不会再遇到同样的问题。总是要求一个在修复前失败、修复后通过的回归测试。

明确“好”的标准:

  • 如果失败是单元测试崩溃,通常要新增一个测试或强化断言。
  • 如果失败是构建、lint 或格式化规则,应该有一个检查来强制该规则,防止此类错误再次出现。

一个有用的模式是要求四件事:放测试的位置、测试命名、测试覆盖的行为,以及简短说明为什么它能防止将来的类似错误。

可复用的附加要求:

  • 写一个会在当前 main 分支上失败、在修复后通过的回归测试。
  • 让测试针对失败类别,而不是只针对触发失败的那一行。
  • 把测试放在:<path or folder>。命名遵循:<你的惯例>。
  • 如果是 lint/build 问题,新增或调整一个检查来强制该规则。
  • 用 2–3 句说明:为什么这个测试会捕获未来的相似 bug。

举例:CI 显示当 API handler 收到空字符串 ID 时 panic。不要只要求“为这一行写测试”。要求一个覆盖无效 ID(空字符串、空白字符、错误格式)的测试。最小修复可能是返回 400 的守护语句。回归测试应断言多个无效输入的行为,这样将来有人重构解析逻辑时 CI 会立刻报错。

如果项目已有测试约定,请写明;如果没有,要求它模仿同包或同文件夹中附近的测试,并保持新测试简洁可读。

可重复使用的逐步工作流

1) 把失败原样给出

粘贴包含错误及其上方 20–40 行的 CI 日志部分。还要粘贴 CI 失败时使用的精确命令和关键环境信息(操作系统、运行时版本、重要标志)。

然后让它用白话复述失败并指出日志中证明该结论的行。如果它不能引用日志,就说明它没真正读日志。

2) 强制先提供最小补丁

要求先给出能让失败命令通过的最小代码改动。反对重构。应用之前让它列出:

  • 将接触的文件
  • 将变化的具体行为
  • 不会改变的内容

3) 重新运行相同命令,保持闭环

应用补丁并在本地运行导致失败的精确命令(或在相同 CI 作业中运行)。如果仍然失败,只粘贴新的失败输出并重复。把上下文减到最小有助于回答更聚焦。

4) 为该失败类别添加回归测试

一旦绿灯,新增一个能在修复前失败、修复后通过的测试。保持针对性:一个测试,一条目的。

再次运行包含新测试的命令以确认你不是把错误“静默”了。

5) 完成一个清晰的 PR 打包

要求简短的提交信息和 PR 描述,包含:出错内容、改动、如何验证以及哪个测试防止复发。审阅者看到清晰理由会更快通过。

从失败输出到修复与测试的现实示例

更快构建应用,修复更小
通过聊天处理 Web、服务端或移动应用,同时保持改动小且易于审查。

一个常见场景:本地一切正常,某个小改动让 CI 在运行时失败。下面是一个来自 Go API 的简单例子:handler 开始接受仅包含日期的值(2026-01-09),但代码仍只按完整的 RFC3339 时间戳解析。

这是应该粘贴的简短片段(保短但包含错误行):

--- FAIL: TestCreateInvoice_DueDate (0.01s)
    invoice_test.go:48: expected 201, got 400
    invoice_test.go:49: response: {"error":"invalid due_date: parsing time \"2026-01-09\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"\" as \"T\""}
FAIL
exit status 1
FAIL	app/api	0.243s

现在用一个强制引用日志、最小修复并带测试的提示:

You are fixing a CI failure. You MUST use the log to justify every claim.

Context:
- Language: Go
- Failing test: TestCreateInvoice_DueDate
- Log snippet:
<PASTE LOG>

Task:
1) Quote the exact failing line(s) from the log and explain the root cause in 1-2 sentences.
2) Propose the smallest possible code change (one function, one file) to accept both RFC3339 and YYYY-MM-DD.
3) Show the exact patch.
4) Add one regression test that fails before the fix and passes after.
Return your answer with headings: Evidence, Minimal Fix, Patch, Regression Test.

一个好的回答会指出解析格式不匹配,然后在一个函数里做一处小改动(例如在 invoice.go 中的 parseDueDate)先尝试 RFC3339,再回退到 2006-01-02。不要重构或新增包。

回归测试是护栏:发送 due_date: "2026-01-09" 并期望 201。如果以后有人移除回退解析,CI 会以相同的失败类别再次报错。

常见浪费时间的错误(以及如何避免)

最快浪费一小时的方式是只给出问题的裁剪视图。CI 日志很吵,但有用的部分通常在最后错误之前约 20 行左右。

一个陷阱是只粘贴最后一行红字(例如“exit 1”),而隐藏了更早的真实原因(缺失环境变量、快照失败或第一个崩溃的测试)。解决办法:包括失败命令和第一个真实错误所在的日志窗口。

另一个时间陷阱是允许模型一路“清理”代码。额外的格式化、依赖升级或重构会让审查更难、也更容易引入其他问题。解决办法:把范围锁定为让失败步骤通过所需的最小代码改动,并拒绝任何无关改动。

一些需要警惕的模式:

  • 只贴最后一行错误:包括失败命令和第一个错误。
  • 让模型改依赖或无关文件:要求最小 diff 并为每个被改文件给出理由。
  • 接受未针对 CI 命令验证的修复:重新运行相同命令确认。
  • 写一个即使 bug 返回也能通过的测试:要求测试在老代码上失败、新代码上通过。
  • 把不稳定的测试和真实回归混在一起:判断是非确定性(时序、网络、顺序)还是稳定逻辑,然后分别处理。

如果你怀疑是非确定性问题,不要用重试掩盖。去掉随机性(固定时间、设定 RNG 种子、隔离临时目录),让信号清晰。

推送修复前的快速检查

把 CI 日志变成补丁
粘贴失败的 CI 日志并在 Koder.ai 聊天中起草最小可行修复。

在推送前做一次简短的理智检查,目标是确保改动是真实、最小且可复现的,而不是一次侥幸通过。

  • 证据:解释是否引用了确切失败行?
  • 范围:改动是否仅限于修复该失败所需?
  • 因果:是否说明为何该改动会把失败翻转为通过?
  • 可复现:是否重新运行了精确的 CI 命令(相同标志、相同工作目录)?
  • 回归:新增测试在修复前会失败、修复后会通过?

最后,运行比单个失败作业稍宽一些的测试(例如 lint+单元测试)。常见的问题是某个修复通过了原始作业,但破坏了其它目标。

下一步:把这个流程变成习惯

如果你希望这个方法长期为你节省时间,就把提示和响应格式当作团队流程的一部分。目标是可重复的输入、可重复的输出,减少“神秘修复”破坏其它功能的情况。

把最佳提示保存为仓库片段并固定到团队聊天里。目标是:

  • 将 CI 失败按类型标注(lint、单元、集成、打包、部署)。
  • 当某类失败重复出现,新增一个测试或检查来在更早阶段捕获它。
  • 保持风险实验可回滚,以便快速撤销。

如果你偏好以聊天为中心的工作流,可以在 Koder.ai 里运行相同的修复-测试循环,使用快照来试验,准备好后再把源码导出回常规仓库。

常见问题

Where in a CI log should I look first when a job fails?

先看第一个真正失败的地方,而不是最终的 exit 1

  • 找到最早显示 发生了什么 的那一行(测试名、文件:行、命令)。
  • 往上读约 20–40 行以获取设置/上下文信息。
  • 在修复第一个错误前,忽略后续的“级联”错误。
How do I stop an AI from guessing and giving a generic fix?

让它证明它确实读了日志。

使用这样的约束:

  • “引用你用到的确切失败行。”
  • “一句话诊断。”
  • “最小修复:1–3 处修改并给出确切文件路径。”
  • “如果日志不完整就停下来只问最少的信息。”
What does “smallest fix” actually mean for a CI failure?

把默认目标设为让失败步骤通过的最小补丁。

通常意味着:

  • 一处有针对性的代码修改(守护语句、修正导入/路径)。
  • 针对 CI 的一项配置调整。
  • 恢复一个意外的破坏性改动,而不是重新设计。

在 CI 绿灯前,避免“清理”类的大改动。

What should I include when I ask for help with a failing CI run?

粘贴足够的上下文以便重现失败,而不是仅仅最后一行红色日志。

包括:

  • 精确的 CI 命令(例如 go test ./..., npm test, flutter test 等)。
  • 第一个错误行到结束的日志。
  • 错误中提到的文件路径。
  • 最近的改动(依赖升级、CI 配置更改、PR 摘要)。
  • 是否存在不稳定性(几次失败加一次通过)。
Can I explicitly tell the model not to refactor or reformat anything?

可以——用简单明了的语言重复你不允许的内容并把它写进约束里。

示例约束:

  • “不允许重构、重命名、格式化或升级依赖。”
  • “只改为修复所需的文件。”
  • “如果行为改变,要明确说明具体改变并解释其正确性。”

这样可以让回复更聚焦、更易于审查。

If CI shows multiple failures, which one do I fix first?

先修最早出现的真实错误。

  • 后续错误通常是被第一个错误触发的(比如构建失败→测试/lint 没有正确运行)。
  • 如果错误互相独立,优先修复阻塞性更强的那个(通常是构建/格式化在集成测试之前)。

不确定时,让模型识别日志中的第一个失败步骤并优先处理它。

How can I tell if a CI failure is flaky, and what should I do?

把不稳定性视为需要消除随机性的信号,而不是增加重试次数。

常见的稳定方法:

  • 固定时间(注入时钟、使用固定时间戳)。
  • 固定随机数种子。
  • 避免网络调用(使用 mock/stub)。
  • 使用独立的临时目录和唯一端口。

一旦变为确定性,最小修复通常就很明显了。

What’s the best way to verify the fix matches CI and isn’t a lucky pass?

要求给出 CI 使用的精确命令,然后在本地运行它。

  • 使用与 CI 相同的命令和参数。
  • 尽量匹配关键环境版本(Go/Node/Flutter 版本、操作系统)。

如果本地难以复现,要求在仓库里给出一个最小重现用例(一个触发相同错误的单测或目标)。

What makes a good follow-up test after fixing a CI failure?

写一个重点明确的回归测试:修复前会失败,修复后会通过。

好的目标包括:

  • 导致失败的边缘情况(null 输入、时区、舍入、权限)。
  • 稍广一层的“故障类别”(例如多个无效 ID,而不是只有一个)。

如果是 lint/构建失败,把等同的“测试”变成更严格的 lint 规则或检查。

How do I iterate quickly without turning my repo into a mess while debugging CI?

使用快照或回滚机制使实验易于撤销。

一个实用的循环:

  • 做最小改动。
  • 运行确切的失败命令。
  • 如果仍然失败,回退到上一个快照并尝试另一个最小补丁。

在 Koder.ai 中使用快照可以快速迭代,而不会把实验性的改动混入最终补丁。

Related posts