从 CI 失败到可合并:深入解析 Remotion 的 pr-ready Agent 技能
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
本篇以 Remotion 单仓库中的 pr-ready 技能定义 为主体,系统讲解"PR 未就绪"的三种典型状态(CI 失败、合并冲突、本地未提交/未推送的变更)如何被逐项诊断并修复,最终把 PR 拉回到可合并状态。读完后,你既能复现该技能的四步初始检查清单与两类故障处理流程,也能从 Install and Test 工作流、CI 规划脚本 和 pre-commit 钩子 的源码层面,理解这套"本地预检 + CI 兜底"体系是如何落地的。
pr-ready 技能的定位:三类"未就绪"状态
Remotion 仓库在.agents/skills/目录下维护了一套面向 AI 编码代理(Agent)的技能库,pr-ready是其中负责"把 PR 拉回就绪状态"的技能。其 frontmatter 元数据为:
name: pr-ready description: Resolve CI failures, merge conflicts, or local branch changes to get a PR ready文档开宗明义地定义了触发条件——当 pull request 处于以下任一状态时,该技能应当被启用:
- CI 失败(a CI failure)
- 合并冲突(a merge conflict)
- 存在未提交或未推送的变更(uncommitted or unpushed changes)
技能的目标只有一句话:"Bring the pull request back to a ready state"(把 PR 恢复到就绪状态)。这一设计思路值得注意:它不是教开发者如何写代码,而是定义了一个收敛型工作流——先诊断,再按类别修复,最后给出可验证的就绪结论。在 merge 技能 中可以看到它被嵌入更大的循环:/merge负责等待 PR 可合并、区分真失败与偶发失败(flake)、并最终执行合并,而其中"解决合并冲突"和"修复真实 CI 失败"两个分支都会显式委托给pr-ready工作流完成,形成清晰的职责划分。
诊断阶段:四步初始检查
技能要求动手修复前必须先完成四项检查,顺序上体现了"先看本地、再看远端"的原则:
- 当前 git 分支与工作树状态——使用
git status查看分支名、未暂存/已暂存的改动; - 本地分支是否有尚未推送的提交——即
HEAD与origin/<branch>之间是否存在差距; - PR 分支与基线分支是否存在合并冲突;
- 当前 PR 的 checks 或 CI 失败——通过
ghCLI 查询。
这四项检查对应了 PR 的三种"未就绪"成因:第 1、2 项覆盖"本地变更未同步",第 3 项覆盖"基线分支前进导致冲突",第 4 项覆盖"远端检查不通过"。只有当没有 CI 失败、没有合并冲突、也没有未提交或未推送的变更时,技能要求直接报告"PR 已经就绪",而不是做任何多余操作——这是一个明确的幂等性约束,避免 Agent 在一切正常时进行无意义的"修复"。
在 Remotion 这样的 Turborepo 单仓库中,第四项检查尤其重要,因为 CI 并非简单的"跑全部测试"。下面先讲两类故障的处理流程,再展开本地预检与 CI 的实际结构。
处理合并冲突:保留双方意图的再同步
当确认存在合并冲突时,技能规定了四步操作:
- 更新本地的基线分支引用(Update the local base branch reference)——即先
git fetch,确保本地的origin/main是最新的; - 将 PR 分支 rebase 或 merge 到基线分支之上,且明确"遵循仓库现有的工作流"(following the repository's existing workflow);
- 谨慎地解决冲突,原文的措辞是"preserve both the PR intent and upstream changes"——既要保住 PR 的改动意图,也要保住上游的变更,不能简单取一侧;
- 对受影响的包运行相关的格式化、测试或构建(Run the relevant formatting, tests, or builds for the affected packages)。
第 4 步与仓库的本地检查体系直接对应。pr 技能 给出了提交前的标准命令序列:先对实际改动的文件路径运行 Oxfmt 格式化(bunx oxfmt <changed-file-or-package-directory>... --write,注意是传入真实路径而非假设仓库根目录有src),然后执行:
bun run build bun run stylecheck以"确保能编译并且 CI 的 lint/格式化检查会通过"。对照根目录 package.json 的 scripts 定义,这两条命令的实际含义是:
"stylecheck": "turbo run lint formatting --no-update-notifier && bun run checkskills", "build": "turbo run make --no-update-notifier"也就是说,冲突解决后的"验证"不止是类型检查通过,还包括checkskills对.agents/skills/技能目录链接一致性的校验(checkskills会依次运行sync-agent-skills.ts --check、sync-embedded-skills.ts --check、sync-readme.ts --check和validateskills链接校验)——这意味着修改技能文件本身也是会被检查项覆盖的变更。
merge 技能 还补充了冲突修复后的闭环:提交并推送解决方案后,要"从循环顶部重新开始",即用gh pr view --json number,url,state,isDraft,baseRefName,headRefName,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup重新确认mergeable与mergeStateStatus状态,而不是假设推送后冲突就自动消失。
处理 CI 失败:修根因,不绕过
对于 CI 失败,技能的五条规则是:
- 用
ghCLI 查看失败 check 的日志; - 修复根本原因,而不是重试或绕过失败(Fix the underlying cause instead of retrying or bypassing the failure);
- 运行覆盖该失败的相关本地检查;
- 必要时提交修复;
- 如果这次修复产生了未推送的提交,推送前先征求用户确认。
第 2 条是整个技能中最有立场的一条:它明确禁止"点重跑"式的掩盖。这一立场在 flake 技能 中得到了精确化的边界——pr-ready说"不要盲目重试",而flake技能则定义了什么情况下重试才是合法的:同一 run 在无任何代码改动时重跑通过、基础设施/网络类错误(如fetch failed、连接重置、runner 供给错误)、或与 PR 改动明显无关的超时/竞态/端口冲突。满足这些条件才可判定为 flake,并用gh run rerun <run-id> --failed只重跑失败的 job,同时把失败签名记录到统一的追踪 issue 中;否则确定性错误(类型错误、lint 错误、快照断言)必须走修复路径。
merge技能给出了查看失败日志的具体命令组合:
gh pr checks --watch=false gh run view <run-id> --json databaseId,status,conclusion,url,jobs gh run view <run-id> --log-failed第 5 条"推送前征求确认"则体现了 Agent 工作流中的权限边界:本地修复和提交属于安全操作,但git push会触发新一轮 CI(且 push.yml 中的concurrency配置会取消 PR 分支上进行中的旧 run),属于影响远端状态的操作,需要用户确认。
本地预检体系:pre-commit 钩子与增量格式化
"运行覆盖失败的本地检查"之所以可行,前提是仓库有一套与 CI 对齐的本地检查链路。从源码看,这条链路由三层构成。
第一层:git 钩子接管。根 package.json 的prepare脚本会执行git config core.hooksPath .githooks,把钩子目录指向 .githooks。其中pre-commit钩子的全部内容只有一行:
#!/bin/sh bun pre-commit.ts第二层:pre-commit.ts 的增量格式化逻辑。该脚本用 Bun 的 shell API 分别收集两类文件:
const staged = await $`git diff --cached --name-only --diff-filter=ACMR`.text(); const unstaged = await $`git diff --name-only`.text();随后按packages/<dir>/前缀把每个变更文件归属到所属包,读取该包的package.json:凡是声明了scripts.format的包收集进formatPackageDirs,声明了scripts.lint的包收集进lintPackageNames。对收集到的包目录逐个执行bun run --cwd <dir> format(即 Oxfmt 格式化),最后一步尤为关键——
// Re-stage originally staged files so formatting changes are included in this commit if (stagedFiles.length > 0) { await $`git add ${stagedFiles}`; }它会把格式化产生的改动重新暂存,避免"提交后工作树又脏了"的常见尴尬:否则提交完立刻git status出现未暂存的格式 diff,恰恰会落入pr-ready所定义的"未提交变更"未就绪状态。这正是本地钩子与 PR 就绪性检查之间的直接衔接点。
第三层:Turbo 任务图驱动的命令面。根目录 scripts 中,stylecheck(对应 CI 的 Linting 作业)、formatting(turbo run formatting)、build(turbo run make)以及完整 CI 等价命令ci(turbo run make test --concurrency=1)都构建在 Turbo 之上。AGENTS.md 进一步约定了代理使用的标准命令:依赖安装用bun install,全量构建用bunx turbo run make,测试与 lint 用bunx turbo run lint test,单包构建用--filter='<package-name>',并明确要求"用bunx而不是npx运行包二进制"。技能库中的 formatting 技能 则把"格式化通过"定义为"bun run stylecheck退出码为 0",并特别注明"不要把lambda-go的错误当作阻断项"——这是一个仓库特有的边界条件,说明本地验证时某些子系统的告警不应触发修复循环。
CI 目标结构:Install and Test 工作流与 affected 规划
理解pr-ready所面对的 CI 失败,绕不开 push.yml。该工作流名为 "Install and Test",由 push 到main或 pull_request 事件触发,其骨架是一个"先规划、后分派"的两段式结构:
规划段(affected job)。首个作业 "Determine affected tests" 只做一件事:
- name: Query Turborepo task graph and create CI plan id: query run: bun .github/scripts/ci.ts planci.ts 会查询 Turborepo 任务图,产出一个带schema_version的 CI 计划(CiPlan类型包含full_ci、lambda、nextjs、browser、webrenderer、ssr、monorepo、templates、build、bundle_example等布尔开关与build_matrix)。每个测试套件到 Turbo 任务的映射硬编码在该文件中:
const TASKS_BY_SUITE = { lambda: ['testlambda'], nextjs: ['testnextjs'], browser: ['testwebcodecs', 'teste2e'], webrenderer: ['testwebrenderer', 'testbrowserstudio'], ssr: ['testssr'], monorepo: ['testmonorepo'], templates: ['testtemplates'], build: ['build', 'make', 'test'], bundle_example: ['bundle-testbed'], } as const;也就是说,PR 只改了@remotion/shapes时,lambda、nextjs等作业会因计划开关为 false 而被if条件整体跳过,CI 失败面被收敛到真正受影响的套件上——这也解释了pr-ready中"运行相关的(relevant)本地检查"的措辞:相关性与 CI 计划的 affected 判定是同一套 Turborepo 依赖图逻辑。
分派段(并行测试矩阵)。各作业均以needs: affected加输出开关驱动,典型如:lambda-tests(40 分钟超时,bun run testlambda加packages/it-tests下的 lambda/cloudrun 集成测试)、browser-tests与webrenderer-tests(运行在macos-latest,需bunx playwright install --with-deps)、ssr-tests与monorepo-tests(Node 16)、template-tests(ubuntu/macos/windows 三平台矩阵,fail-fast: false)。
Lint 作业(push.yml 中 "Linting + Formatting")。这一作业与本地stylecheck直接对应,且在 PR 事件下会带上--affected增量运行:
- name: Perform stylecheck run: | bunx turbo run lint formatting ${{ github.event_name == 'pull_request' && needs.affected.outputs.full_ci != 'true' && '--affected' || '' }} --no-update-notifier bun run checkskills - name: Test CI planning contract run: bun test ./.github/scripts/ci.test.ts值得留意的是末尾那行:bun test ./.github/scripts/ci.test.ts会对 CI 规划脚本本身跑契约测试,保证"规划段"的输出结构稳定。
收口段(required-ci 作业)。工作流末端有一个 "Required CI" 作业,needs了包括affected、lint、build、各测试作业在内的全部作业,并执行bun .github/scripts/ci.ts validate:把规划段输出的plan与各作业实际结果(经CI_RESULTSJSON 汇总)做交叉校验。从源码结构看,这意味着 CI 的最终绿灯不取决于"跑过的作业都绿了",而取决于"规划要求跑的作业确实按预期跑过并通过"——规划漏判或作业被意外跳过都会在这里被兜底捕获。
技能间的协作:pr、pr-ready、merge 与 flake
把pr-ready放回整个技能库的语境中,Remotion 的 PR 生命周期被拆分为四个可组合的技能:
- pr 技能:负责"从本地变更到 PR 创建"。它要求先确认不在
main分支、检查gh pr status是否已有 PR、对改动路径运行 Oxfmt、执行bun run build与bun run stylecheck、单次提交后用git push -u origin HEAD推送(且"除非用户要求,绝不 force push"),最后用gh pr create --title ... --body-file <临时md文件>创建 PR——注意它要求 PR 正文写入临时 Markdown 文件再通过--body-file传入,而非在 shell 内联传递。 - pr-name 技能:约束 PR 标题格式,前缀取受影响包
package.json中的精确name值,形如`@remotion/shapes`: Add heart shape;纯文档用Docs:、内部测试用Internal:等特殊前缀。 - pr-ready 技能(本文主体):PR 已存在但未就绪时的修复收敛器。
- merge 技能:就绪后的守门人,用
gh pr checks --watch --interval 30等待检查,全部通过且可合并时执行gh pr merge --merge --delete-branch。
四者的衔接关系可以概括为一条流水线:pr创建 PR 并保证首次提交前本地stylecheck已通过 → CI 运行期间基线分支前进或检查失败 →pr-ready按"四步诊断 + 分型修复"把 PR 拉回就绪 →merge的循环确认可合并性后合并;其中 CI 失败的"修复 vs 重跑"分界由flake技能给出可操作的判据。这套拆分让每一步都有独立、可验证的入口与出口,例如pr-ready的出口要么是"已修复并(经确认后)推送",要么是"报告 PR 已经就绪",没有第三种含糊状态。
小结:可复用的"PR 就绪"检查清单
回到 pr-ready/SKILL.md 本身,它的全文不足四十行,却给出了一个可以直接搬用到任何仓库的操作框架:
| 阶段 | 动作 | 验证依据(以 Remotion 仓库为例) |
|---|---|---|
| 诊断 | git status、核对未推送提交、核对基线冲突、gh查询 PR checks | 三项全否且 checks 全绿 → 报告就绪 |
| 冲突 | fetch 基线 → rebase/merge → 双向保留意图解冲突 → 跑受影响包的 format/test/build | bun run build、bun run stylecheck(含checkskills)退出码 0 |
| CI 失败 | gh查日志 → 修根因(flake 判据除外)→ 本地覆盖检查 → 提交 → 推送前征求确认 | 对应 CI 作业如 "Linting + Formatting"、"Required CI" |
| 本地守门 | pre-commit 钩子对 staged+unstaged 文件做包级 Oxfmt 格式化并重新暂存 | pre-commit.ts |
这套技能的价值不在于任何单条命令,而在于它把"PR 就绪"定义成了一组可观察、可判定、可收敛的条件,并且每个条件都有对应的仓库内证据来源:诊断用git/gh,修复后的验证用与 CI 同源(同由 Turborepo 任务图驱动)的本地命令,最终由 Required CI 作业 的规划-结果交叉校验兜底。对于维护大型单仓库的团队,这种"技能即工作流契约"的组织方式,让 Agent 与人类贡献者执行的是同一套收敛逻辑。
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考