首页
/ Remotion flake 技能实战:在 Tracker Issue 中记录 CI 不稳定失败并自动恢复测试套件

Remotion flake 技能实战:在 Tracker Issue 中记录 CI 不稳定失败并自动恢复测试套件

2026-09-06 17:06:53作者:柯茵沙

Remotion 仓库为 Agent 提供了一组内部技能(internal skills),存放于 .agents/skills/ 目录下。其中 flake 技能 专门解决一类高频但棘手的工程问题:CI 检查"看起来"失败,实际是 flaky(不稳定/偶发)失败。读完本文,你将掌握一套完整的 flake 处理流程——如何用 gh CLI 定位失败的 PR、workflow run、job 与日志,如何判定一个失败是否真的是 flake,如何生成稳定的失败签名(signature),如何把失败登记进 tracker issue 的 Markdown 表格,以及如何选择正确的重跑(rerun)策略让受影响的测试套件恢复绿灯。

flake 技能的定位与适用场景

根据 skill-locations 技能 的约定,Remotion 把"仅内部 Agent 使用"的技能放在 .agents/skills/<skill-name>/SKILL.md,把可分发的公共技能放在 packages/skills/skills/ 下。flake 属于前者:它是维护者内部的工作流剧本(playbook),触发条件是"一个 Remotion CI 检查失败且看起来是 flaky",或用户显式请求执行 /flake

技能 frontmatter 中声明的核心职责有三件事:

  1. 跟踪 Remotion 的 CI flake,统一登记在 tracker issue #8375;
  2. 对重复出现的失败签名(signature)累加计数;
  3. 在未给出 PR 时,主动发现失败的 PR 检查,并重跑 flaky 的 GitHub Actions job。

技能的总体目标可以概括为一句话:把 flaky 失败记录进 issue #8375,并让受影响的测试套件重新跑通。对每一个"已确认或疑似"的 flake,标准动作是三步:

  1. 定位 PR、workflow run、job、失败的 step,并提炼简洁的失败签名;
  2. 更新 tracker issue:签名已存在则累加其 Count,是新签名则追加一行(计数 1、workflow/job、首次出现日期、证据 URL);
  3. 重跑 flaky 的 job 或全部失败 job。

定位失败:gh 命令集

技能明确要求优先使用 gh CLI 而不是 GitHub App,原因是 GitHub App 可能无法暴露完整的 Actions 日志和写入权限。针对不同的输入上下文,给出了四组命令。

场景一:用户提供了 PR 编号

gh pr view <pr-number> --repo remotion-dev/remotion --json number,title,headRefName,statusCheckRollup
gh pr checks <pr-number> --repo remotion-dev/remotion --watch=false

第一条拿到 PR 的基础信息和完整的 check rollup(所有检查项的状态汇总),第二条直接列出各 check 的通过/失败明细,--watch=false 避免阻塞等待。

场景二:用户提供了 workflow run 或 job URL

gh run view <run-id> --repo remotion-dev/remotion --json databaseId,attempt,displayTitle,event,headBranch,headSha,status,conclusion,workflowName,createdAt,updatedAt,url,jobs
gh api repos/remotion-dev/remotion/actions/jobs/<job-id>/logs

注意 attempt 字段的含义——同一个 run 重跑后会形成多个 attempt,这直接决定了下一条命令的选择。第二条用 raw API 拉取指定 job 的完整日志,用于阅读失败 step 的实际输出。

场景三:没有 PR 上下文

当用户只说"CI 挂了"而没有给 PR 时,需要主动扫描最近的开放 PR 和最近的失败 run:

gh pr list --repo remotion-dev/remotion --state open --limit 30 --json number,title,headRefName,statusCheckRollup
gh run list --repo remotion-dev/remotion --workflow "Install and Test" --status failure --limit 20 --json databaseId,displayTitle,event,headBranch,headSha,conclusion,createdAt,url,workflowName

这里筛选的 workflow 名称 "Install and Test" 在仓库中是真实存在的:它定义在 .github/workflows/push.yml 中(name: Install and Test),由 push 到 main 和 pull_request 事件触发。该 workflow 的结构也解释了为什么它容易产生 flake:它先用一个 affected job 通过 bun .github/scripts/ci.ts plan 查询 Turborepo 任务图来裁剪出受影响的测试子集(lambda、nextjs、ssr、browser、webrenderer 等矩阵 job),再按输出条件执行对应的集成测试 job。测试矩阵被裁剪、并发取消(concurrency + cancel-in-progress)都会让超时类失败更具偶发性。

场景四:针对重跑过的 run,先查原始 attempt

一个常见陷阱:run 已经 rerun 过一次,直接看当前 attempt 的日志可能全是绿的,无法判断第一次失败是不是 flake。技能要求在判定前检查第一次 attempt:

gh api repos/remotion-dev/remotion/actions/runs/<run-id>/attempts/1/jobs --paginate
gh api repos/remotion-dev/remotion/actions/jobs/<failed-job-id>/logs

即:拉取 attempt 1 的 job 列表,再取其中失败 job 的日志。如果同一 run/job/step 在无代码改动的情况下重跑后成功了,这正是判定 flake 的第一条判据(见下一节)。

flake 判定标准

技能给出了明确的判定规则——满足以下至少一条即可视为 flake:

  • 重跑验证:同一个 run、job 或失败 step 在没有代码改动的情况下重跑后成功了;
  • 基础设施/网络类故障:例如 fetch failed、连接被重置(connection reset)、包管理器下载失败、缓存服务故障、runner 供给错误(provisioning error);
  • 与 PR 改动无关的偶发失败:超时、竞态(race)、浏览器断连、端口冲突、外部服务间歇性故障,且该 PR 的改动"不可能"触及出问题的区域。

同时给出了反例边界:不要把确定性的类型错误(type error)、lint 错误、snapshot 不匹配、或"由源码行为变更导致的测试断言失败"记入 tracker,除非有证据表明它们在重试后能通过。这条边界很重要——tracker 的价值在于把"噪声失败"与"真实回归"分开,误登记会把真实的 bug 淹没在 flake 表格里。

仓库中自带的 flake 缓解机制

判定标准不是凭空而来的。以 flake 文档示例中反复出现的 @remotion/it-tests#testssr 为例,packages/it-tests/package.json 里定义了 "testssr": "bun src/run-ssr-tests.ts"。而这个脚本本身就是一个精心设计的防 flake 包装器:packages/it-tests/src/run-ssr-tests.ts 对每个测试文件最多尝试 2 次,用 Bun.spawn 在独立子进程中运行 bun test <file> --timeout 40000,并设置了 90 秒看门狗——超时先发 SIGTERM,2 秒后再补 SIGKILL(非 Windows 下直接 kill 整个进程组);第一次超时会在干净进程中重试并打印 timed out. Retrying in a clean process.,第二次才超时则抛出 timed out twice

从源码结构看,这正是 flake 文档里"timeout / race / browser disconnect"那类判据的现实来源:单个测试文件首次超时时,CI 未必会失败(脚本会自动重试一次),而"两次都超时"才会真正让 job 失败——这类失败在负载较高的 runner 上确实具有偶发性,是 tracker 表格的典型登记对象。同理,oven-sh/setup-bun@v2.1.2 failed with TypeError: fetch failed 这类签名对应的是 push.yml 中每个 job 都会执行的 setup-bun action 拉取失败(bun 版本固定为 1.3.3),属于典型的网络类基础设施故障,与 PR 代码无关。

签名(Signature)规则

签名是 tracker 表格中"把重复失败归为一组、又足够具体以便处理"的关键。技能的规则:

  • 能给出 package 或 suite 名就给,例如 @remotion/it-tests#testssr
  • 能给出测试名就给
  • 包含根因错误文本,但要剥离时间戳、文件路径、随机 ID、端口号、行号、ANSI 颜色码——这些是"同一次签名、不同次实例"的主要噪声来源;
  • workflow/job 信息单独放在 tracker 行的对应列里,除非不放进签名就无法区分,否则不要混入签名。

文档给出的两个"好签名"示例:

  • `oven-sh/setup-bun@v2.1.2` failed with `TypeError: fetch failed`
  • `delayRender()` timeout while running `Render video with browser instance not open` in `@remotion/it-tests#testssr`

两个示例分别代表"基础设施类"和"测试超时类"两种典型 flake:前者签名锚定在失败的 action 版本上,后者锚定在"渲染模式 + 包#suite + 根因"上。第二个示例中的测试名 Render video with browser instance not open 确实存在于 it-tests 的 SSR 测试中(如 packages/it-tests/src/ssr/render-media.test.ts),说明签名规则是贴着真实测试套件写的。

更新 Tracker Issue

登记环节围绕 tracker issue #8375 的正文展开。先读取当前正文:

gh issue view 8375 --repo remotion-dev/remotion --json body --jq .body

操作时有两条硬性约束:

  • 编辑完整正文、保留表格结构——不能只贴增量;
  • 替换后的 Markdown 必须写入临时文件并用 --body-file 提交,不要内联传递多行 Markdown(避免 shell 转义把表格弄坏)。

tracker 表格的格式:

| Count | Signature | Workflow / job | First seen | Evidence |
| ---: | --- | --- | --- | --- |
| 1 | `signature` | `Workflow` / `Job` | YYYY-MM-DD | <对应 Actions run 的 URL> |

累加已有行的规则:

  • 除非旧证据链接已失效、或新链接比旧链接更有用,否则只改 Count
  • First seen 始终保持最早一次出现的日期。

追加新行的规则:

  • 证据列优先使用 GitHub Actions 的 job URL(比 run URL 更能直接定位失败步骤);
  • 首次出现日期以日志中的时间戳为准,而不是本地机器日期——CI 日志时间戳与操作者本地时区可能不一致,用日志时间戳能保证跨维护者登记的一致性。

编辑完成后必须回读验证:

gh issue view 8375 --repo remotion-dev/remotion --json body --jq .body

这一步能捕获表格被破坏、行被重复追加等静默错误。

重跑策略

技能按三种情况给出了从轻到重的重跑手段:

只重跑失败 job(首选)——大多数 flake 场景用这一条就够了:

gh run rerun <run-id> --repo remotion-dev/remotion --failed

run 已结束、只有一个已知 flaky job 需要重跑时,用 raw API 直接重跑单个 job,避免整个 run 重来:

gh api --method POST repos/remotion-dev/remotion/actions/jobs/<job-id>/rerun

flaky 的 suite 仍在运行中、继续等待会阻塞当前任务时,先取消再重跑:

gh run cancel <run-id> --repo remotion-dev/remotion
gh run watch <run-id> --repo remotion-dev/remotion --exit-status
gh run rerun <run-id> --repo remotion-dev/remotion --failed

这里有一个容易踩坑的细节:取消会使 run 的最终状态变成 failure,因此 gh run watch 可能以非零状态退出——技能明确要求此时继续执行后面的 rerun 步骤,不要被非零退出码误导而中断流程。

收尾汇报

技能规定处理结束后向用户汇报三项内容:

  • tracker issue 中新增或累加了哪一行;
  • 采取的重跑动作,附 run 或 job URL;
  • 还有哪些仍在运行的检查项是刻意没有动的(避免误杀正在进行中的正常 job)。

这一收尾设计保证了每次 flake 处理都是可审计的:登记动作、重跑动作、保留动作三件事都有据可查,tracker 表格随着时间累积后,本身就成为该仓库 CI 稳定性的观测数据——哪些签名 Count 高、集中在哪个 workflow/job,就是下一步修复(比如调整测试超时、加固缓存或升级 action 版本)的优先级依据。

小结

flake 技能的价值在于把"CI 挂了"这个模糊事件拆解成一条可复现的操作链:定位(四组 gh 命令覆盖有无 PR、有无重跑的不同上下文)→ 判定(三条阳性判据 + 一条排除边界)→ 签名(去噪后的稳定标识)→ 登记(body-file 全量编辑 + 回读验证)→ 重跑(failed-only、单 job API、cancel 后重跑三档策略)→ 汇报。配合仓库中 run-ssr-tests.ts 这类内置重试脚本和 push.yml 的任务图裁剪机制,它构成了 Remotion 这个大型 monorepo 处理偶发 CI 失败的完整闭环。

登录后查看全文
热门项目推荐
相关项目推荐