首页
/ Gemini CLI CI Skill:监控 GitHub Actions 失败并自动在本地复现的 Skill 实现剖析

Gemini CLI CI Skill:监控 GitHub Actions 失败并自动在本地复现的 Skill 实现剖析

2026-09-05 16:28:40作者:瞿蔚英Wynne

本文围绕 gemini-cli 仓库中的 CI Skill 定义文件 展开,讲解这个专为 Gemini CLI 定制的 "CI Replicate & Status" 技能如何自动发现分支上的 GitHub Actions 运行、以 fail-fast 策略轮询状态,并在检测到失败时立即生成可本地执行的测试/lint 命令。读完本文,你能完整理解该 Skill 的工作流程、命令行参数、失败分类规则,以及底层脚本 ci.mjs 的运行发现、日志抓取与噪音过滤机制,从而掌握"远程 CI 失败 → 本地一键复现"这一缩短修复周期的实战方案。

1. Skill 定位:从远程 CI 到本地复现的自动化桥梁

SKILL.md 的 frontmatter 将该技能描述为:提供高性能、fail-fast 的 GitHub Actions 工作流监控,以及对 CI 失败的自动化本地验证,并且"运行发现是自动的——只需提供分支名"。原文档列出了三大核心能力:

  • Automatic Replication(自动复现):自动监控 CI,一旦检测到失败,立即在本地执行脚本建议的测试或 lint 命令(输出中标记为 🚀)。
  • Real-time Monitoring(实时监控):对当前分支上所有并发工作流提供聚合状态行。
  • Fail-Fast Triage(快速分诊):在第一个 job 失败时立即停止轮询,输出结构化失败报告。

这个 Skill 遵循 Gemini CLI 的 Agent Skills 机制(可参见 docs/cli/skills.md 中对技能发现与管理的说明),由 SKILL.md 声明用途,由 scripts/ 目录下的脚本承担实际的 CLI 调用工作。它的核心价值在于:把"看 CI → 找失败 job → 翻日志 → 定位失败测试文件 → 本地重跑"这条人工链路压缩成一次命令调用。

2. 两种工作流:replicate(默认)与 status

原文档定义了两种用法,底层调用的是同一个脚本,区别在于交互深度:

2.1 CI Replicate(replicate)——默认路径

node .gemini/skills/ci/scripts/ci.mjs [branch]
  • 行为:触发后 Agent 监控 CI,一旦检测到失败,立即自动执行所有建议的测试或 lint 命令(标记 🚀),无需人工干预。
  • 运行发现:脚本自动找到该分支最新的活动或近期 run,不要手动用 gh run list 搜索 run ID,直接给分支名即可。
  • 目标:无人工介入地复现失败,然后进入代码分析与修复阶段。

2.2 CI Status(status)——手动三步走

node .gemini/skills/ci/scripts/ci.mjs [branch] [run_id]
  • 第二个参数 [run_id] 用于指定某个特定的历史 run;原文档明确说明,除非用户明确要查看某个历史 run,否则不应手动搜索 run_id,只需提供分支名。
  • Step 1(Monitor):带分支名执行工具。
  • Step 2(Extract):从输出中提取建议的 npm testnpm run lint 命令(标记 🚀)。
  • Step 3(Reproduce):在本地执行这些命令确认失败。
  • 行为:脚本每 15 秒轮询一次;检测到失败后退出并打印结构化报告,附带需要在本地执行的确切命令。

3. 参数与仓库识别:脚本入口逻辑

ci.mjs 的入口代码可以看到参数解析与仓库识别策略:

const BRANCH =
  process.argv[2] || execSync('git branch --show-current').toString().trim();
const RUN_ID_OVERRIDE = process.argv[3];
  • 第一个位置参数是分支名,缺省时自动取当前 git 分支git branch --show-current);
  • 第二个位置参数是可选的 run_id 覆盖值。

仓库标识(REPO)通过解析 git remote get-url origin 得到,剥离 github.com/github.com: 前缀和 .git 后缀;若没有 origin 远端则回退到 google-gemini/gemini-clici.mjs)。所有 gh 调用被封装在 runGh() 中:stdout 被管道捕获,stderr 丢弃,任何异常都返回 null 而非抛出——这让脚本在 gh 未认证或网络抖动时能优雅降级,而不是中途崩溃。

4. 运行发现(Discovery):双通道定位目标 run

monitor() 函数实现了原文档承诺的"自动运行发现",实际上用了两条互补的通道ci.mjs):

通道 1:分支 run 列表。 执行

gh run list --branch "<branch>" --limit 10 --json databaseId,status,workflowName,createdAt

优先选择 status !== 'completed' 的活动 run;若没有活动 run,则取最新一条的创建时间,挑出与其相差 60 秒以内 的 run(覆盖一次推送并行触发多个 workflow 的场景)。

通道 2:commit status 反查。 通过 git rev-parse <branch> 拿到 HEAD SHA,再调用

gh api repos/<REPO>/commits/<sha>/status

target_url 中包含 actions/runs/ 的状态记录里用正则 actions\/runs\/(\d+) 提取 run ID,并去重合并进目标列表。这条通道用于处理"间接/链式触发"的 run——即不直接挂在分支名下、但通过 commit 状态关联到的 workflow run。

最终脚本会打印一行 Monitoring workflows: <去重后的 workflow 名列表>,若两个通道都找不到 run,则输出 No runs found for branch <branch>. 并以退出码 0 正常结束(例如本地分支从未推送过 CI)。

5. 轮询循环:聚合状态行与 fail-fast 退出

主循环(ci.mjs)每轮对所有目标 run 做两件事:

  1. gh run view <id> --json databaseId,status,conclusion,workflowName 获取 run 级状态,status !== 'completed' 即视为仍有活动 run;
  2. gh run view <id> --json jobs 拉取全部 job,按 job 的 status/conclusion 聚合四个计数器:in_progress → running、queued → queued、success → passed、failure → failed。

无失败时,脚本用 \r 原地刷新一行聚合状态,这正是原文档所说 "Aggregated status line for all concurrent workflows" 的实现:

⏳ Monitoring 3 runs... 12/20 jobs (10 passed, 0 failed, 2 running, 0 queued)

当所有 run 完成且无失败时打印 ✅ All workflows passed! 并以退出码 0 退出;一旦某轮发现 conclusion === 'failure' 的 job,立即进入失败处理分支并 以退出码 1 退出——这就是 fail-fast 语义:不等其余 job 跑完,第一时间把结构化报告交给 Agent。轮询间隔为 15 秒(setTimeout(r, 15000)),与原文档 "poll every 15 seconds" 的描述一致。

6. 日志抓取与噪音过滤:从原始日志到结构化报告

对每个失败 job,脚本调用 fetchFailuresViaApi(jobId)ci.mjs):

gh api repos/<REPO>/actions/jobs/<jobId>/logs | \
  grep -iE " FAIL |❌|ERROR|Lint failed|Build failed|Exception|failed with exit code"

即只保留命中失败特征的行,maxBuffer 上限设为 10MB。随后 isNoise() 再过滤一轮噪音,命中以下任意特征的行被丢弃(ci.mjs):

  • * [new branch](git fetch 输出)
  • npm warn(NPM 警告)
  • fetching updates
  • node:internal/errors
  • at (stack trace 行)
  • checkexecsyncerror
  • node_modules

这正是 SKILL.md 中 "Noise Filtering" 一节所说的:底层脚本自动过滤 Git 日志、NPM 警告、堆栈冗余,Agent 应聚焦于工具提供的 "Structured Failure Report"。

7. 失败归类与本地命令生成

7.1 失败行的两级归类

对过滤后的每一行失败输出,脚本先尝试 extractTestFile():清除 #[]()|<...> 等标记字符后,用正则 ([\w\/._-]+\.test\.[jt]sx?) 提取 vitest/jest 风格的测试文件路径。若未提取到文件,则按关键词兜底归类:行内含 lintLint Error,含 buildBuild Error,否则 Unknown Fileci.mjs)。

若整个 job 日志一条特征行都没抓到(failures 为空),脚本转而查看 job 的 steps,找到第一个 conclusion === 'failure' 的 step 名,同样按 lint/build 关键词归类为 Lint Error / Build Error / Job Error,并记录 <job.name>: Failed at step "<step>"ci.mjs)。

7.2 生成 npm test -w <workspace> -- <files> 命令

generateTestCommand()ci.mjs)把"失败文件 → 本地测试命令"的转换规则与 monorepo 结构对齐:

失败文件前缀 npm workspace 命令形式
packages/core/... @google/gemini-cli-core npm test -w @google/gemini-cli-core -- <相对文件...>
packages/cli/... @google/gemini-cli npm test -w @google/gemini-cli -- <相对文件...>
Job Error / Unknown File / Build Error / Lint Error (跳过) 不生成测试命令

工作区名与 packages/core/package.jsonpackages/cli/package.json 中的包名完全对应;同一 workspace 的多个失败文件合并进一条命令,多个 workspace 之间用 && 连接。若所有失败都是 lint 类,则输出兜底命令 npm run lint:all。最终报告形如:

❌ Failures detected across 2 job(s). Stopping monitor...

--- Structured Failure Report (Noise Filtered) ---

Category/File: packages/cli/src/ui/components/Editor.test.tsx
  - Editor > renders placeholder text
  ...

🚀 Run this to verify fixes:
npm test -w @google/gemini-cli -- src/ui/components/Editor.test.tsx.x
---------------------------------

报告细节也有节制:每条失败描述超过 500 字符会被截断并标注 [TRUNCATED],每个文件最多展示 10 条、多余部分折叠为 ... and N more,保证输出紧凑、可被 Agent 直接消费。

7.3 与仓库脚本的对应关系

建议命令在根 package.json 中都有真实落点:

  • npm testnpm run test --workspaces --if-present && npm run test:sea-launch,所以 npm test -w <pkg> -- <path> 会按 npm workspaces 语义只跑指定包;
  • npm run lint:all 对应 node scripts/lint.js,即 scripts/lint.js —— 它按平台自动安装并运行 actionlint(检查 GitHub Actions YAML)、shellcheck、yamllint,所以该命令能覆盖 CI 中 lint job 的主要检查面;
  • 另有 npm run linteslint . --cache --max-warnings 0--max-warnings 0 意味着警告也会让 lint 失败),对应纯 JS/TS 代码的 lint 失败。

8. 失败分类与处理动作(原文档规则)

SKILL.md 的 "Failure Categories & Actions" 一节给出 Agent 面对四类失败的标准动作,与脚本的归类一一对应:

类别 Agent 应执行的动作
Test Failures 运行脚本建议的具体 npm test -w <pkg> -- <path> 命令
Lint Errors 运行 npm run lint:all 或对应包的 lint 命令
Build Errors 检查 tsc 输出或构建日志,解决编译问题(对应仓库根 npm run typechecknpm run build
Job Errors gh run view --job <job_id> --log 排查基础设施或环境准备失败

其中 "Job Errors" 是脚本在抓不到任何测试/lint/build 特征、只能定位到失败 step 时的兜底分类,此时自动化复现链路让位于人工(或 Agent 主动)看完整 job 日志。

9. 适用前提与实践要点

结合脚本实现,使用该 Skill 需要注意以下前提与限制:

  • 依赖 gh CLI 且已认证:所有发现、轮询、日志抓取都经 ghrunGh() 对失败返回 null,若 gh 完全不可用,脚本会因找不到 run 而直接以 No runs found 退出(退出码 0),不会报错也不会轮询。
  • 要求 GitHub 远端REPOgit remote get-url origin 解析,非 GitHub 托管的远端无法工作;无 origin 时回退到默认仓库名,仅适用于该仓库的 fork 本地场景。
  • Node 版本:仓库根 package.json 要求 node >= 20.0.0,脚本仅用 node:child_process,无额外依赖,可直接 node .gemini/skills/ci/scripts/ci.mjs 运行。
  • run 发现的时间窗:分支无活动 run 时,只合并与最新 run 创建时间相差 60 秒以内的 run;commit status 通道再补上间接触发的 run。因此"先 git push 再触发脚本"是最稳的用法,脚本启动时 CI 需要已被触发。
  • 测试文件提取的假设extractTestFile() 只识别 .test.ts/.test.tsx/.test.js/.test.jsx 命名,与仓库内 vitest 的测试命名一致;其他命名约定的失败不会进入精确文件级命令,只会落入 Unknown File
  • 退出码语义:失败退出码 1、全绿退出码 0,可直接作为 CI 复现链路中的可判断信号,供上层 Agent 决定"复现成功 → 进入修复"还是"本地通过 → 可能是环境差异,转看 Job Errors"。

小结

这个 CI Skill 的设计可以概括为三层:上层是 SKILL.md 声明的行为契约(自动复现、fail-fast、禁止手动搜 run ID),中层是 ci.mjs 的双通道 run 发现 + 15 秒轮询 + 聚合状态行底层是 grep 特征抓取 + 噪音过滤 + workspace 映射的命令生成。它把 monorepo 中"CI 红了一大片"的场景收敛为一条可复制的本地命令,让 Agent 在第一次 job 失败时就能拿到结构化分诊报告并直接进入复现-修复循环,是"文档声明能力、脚本落实执行"式 Agent Skill 的一个典型范例。

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