Electron PR 分诊工具 triage-prs 详解:基于 gh CLI 按 CI 状态分类 open PR 的完整实践
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
本文围绕 Electron 仓库内置的 triage-prs 技能 展开,讲清这套 "PR 分诊" 工具如何通过ghCLI 加jq把海量 open PR 按 CI 检查状态归类为 green / failing / pending / none 四类,如何与 "PR Triage" 项目看板联动标注评审进度,以及默认的 "GitHub Actions Completed" 伞形检查(umbrella check)背后真实的 GitHub Actions 工作流实现。读完后你将能够独立运行该脚本完成 PR 分诊、按单个检查项(如faraday/cage)过滤、理解每一类判定结果的边界条件,并根据源码自行解释输出结果中的每一列标记。
一、工具定位与文件结构
triage-prs是 Electron 仓库维护者工作流中的一个 AI 技能(skill),由两个文件组成,均位于仓库根目录的.claude/skills/triage-prs/下:
- SKILL.md:技能说明书,定义触发条件(如用户说 "faraday"、"failing prs" 时自动执行)、状态分类表和全部用法示例;
- triage-prs.sh:真正执行分诊的 Bash 脚本,约 258 行,仅依赖
ghCLI 和jq,通过 GitHub REST 与 GraphQL API 拉取数据。
两者通过文件头部的 front-matter 关联:SKILL.md中的name: triage-prs与description字段让 AI 助手在用户请求 "triage PRs"、"列出绿色/可合并 PR"、"找出 CI 失败或仍在跑的 PR" 时触发本技能。该技能由 提交 db65df8492 引入仓库(chore: add triage-prs skill and script)。
它要解决的实际问题很具体:Electron 是一个高频接收 PR 的大型开源项目,单个检查项(如构建矩阵中的macos-x64 / build / build)有成百上千个状态组合,维护者需要快速回答三个问题——哪些 PR 的 CI 全绿可以优先合并?哪些 PR 的 CI 挂了需要作者修复?哪些 PR 还卡在排队或运行中?本工具就是围绕这三个问题设计的命令行分诊器。
二、前置条件:gh CLI、jq 与 read:project 权限
运行脚本前需要满足以下环境要求(脚本在 第 81 行至第 88 行 会显式检查并给出报错):
- 安装并认证 GitHub CLI(
gh auth login)。gh是脚本访问 GitHub 的唯一通道。 - 安装
jq并保证在PATH中。脚本用jq解析gh返回的 JSON(状态汇总、标题、URL、标签、看板字段)。 - (可选)如需展示 "PR Triage" 看板 Status 列,
ghtoken 需要read:project权限。缺失时用以下命令补授:
gh auth refresh -s read:project值得注意的是,脚本并不会因为缺少该权限而整体失败。第 95 行至第 107 行 的实现逻辑是:先用一条 GraphQL 查询探测能否读取项目title(这是需要read:project的最小操作),若返回INSUFFICIENT_SCOPES,则打印一次性提示并自动关闭 Status 列(TRIAGE=0),其余分诊功能照常运行。这种"降级而非报错"的设计保证了核心分诊能力不受权限影响。
三、四类状态模型:green / failing / none / pending
这是本技能的核心概念。每个被扫描的 open PR 都会被归入且仅归入以下四类之一:
| 状态 | 图标 | 含义 |
|---|---|---|
green | ✅ | 所有检查通过(SUCCESS/NEUTRAL/SKIPPED)。 |
failing | ❌ | 至少一个检查失败(FAILURE/ERROR/CANCELLED/TIMED_OUT/…)。 |
pending | 🟡 | 检查存在但部分仍在运行(PENDING/IN_PROGRESS/…)。 |
none | ⚪ | 完全没有检查在跑。 |
这四个类别不是脚本凭空定义的,而是严格映射到 GitHub API 返回的statusCheckRollup条目上。脚本通过一段内嵌的jq程序完成归类,其判定链值得逐行理解(第 168 行至第 215 行):
第一步:把每个检查条目归一化为一个状态值(state_of函数)。statusCheckRollup里混着两种数据结构:GitHub Actions 的CheckRun条目(带.status和.conclusion字段)和旧版StatusContext条目(只有.state字段)。state_of的处理顺序是:
if (.status != null and .status != "COMPLETED") then .status elif ((.conclusion // "") != "") then .conclusion elif ((.state // "") != "") then .state else "PENDING" end这里有一个源码注释里特别点明的坑:未完成的CheckRun其.conclusion是空字符串而非 null,所以必须先按.status判断,否则会把运行中的检查误判为已完成。若所有字段都取不到值,兜底为PENDING。
第二步:把状态值列表收敛为四个类别之一(classify函数)。优先级是"失败优先,其次等待,最后全绿":
if ($s | length) == 0 then null elif any($s[]; . == "FAILURE" or . == "ERROR" or . == "CANCELLED" or . == "TIMED_OUT" or . == "ACTION_REQUIRED" or . == "STARTUP_FAILURE" or . == "STALE") then "failing" elif any($s[]; . == "PENDING" or . == "EXPECTED" or . == "IN_PROGRESS" or . == "QUEUED" or . == "REQUESTED" or . == "WAITING") then "pending" else "green" end可以看到failing覆盖了 7 种失败态(包括STALE和STARTUP_FAILURE),pending覆盖了 6 种等待态;既无失败也无等待的剩余组合(SUCCESS/NEUTRAL/SKIPPED等)归为green;空列表返回null,在调用侧再转成none。
四、默认评估范围:"GitHub Actions Completed" 伞形检查
默认情况下脚本并不逐一看 PR 上的所有检查,而是只看一个名为"GitHub Actions Completed"的检查。这不是一个普通检查,而是 Electron 构建工作流专门设计的伞形检查(umbrella check),它在 build.yml 中定义为gha-donejob:
gha-done: name: GitHub Actions Completed runs-on: ubuntu-latest permissions: contents: read needs: [docs-only, checkout-macos, checkout-linux, checkout-windows, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-x64-ubsan, linux-arm64, build-siso-macos, build-siso-linux, build-siso-windows, windows-x64, windows-arm64] if: always() && github.repository == 'electron/electron' steps: - name: Fail if any needed job failed or was cancelled if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') run: exit 1从源码结构看,这个 job 的作用是把十几个平台构建/测试 job(macOS x64/arm64、Linux x64/asan/ubsan/arm64、Windows x64/arm64 等)的结果聚合成一个对 PR 而言的二元信号:只要needs中任一 job 结果为failure或cancelled就exit 1。if: always()保证即使上游 job 失败它也会运行并发出失败状态。这样分诊脚本只需盯着一个检查,就能判断整条 CI 流水线的最终结果,显著减少误判面。
若想改用其它评估口径,脚本提供--check参数:
# 评估 PR 上的全部检查 .claude/skills/triage-prs/triage-prs.sh --check "" # 只看某一个具体检查 .claude/skills/triage-prs/triage-prs.sh --check "macos-x64 / build / build"五、关键设计:SKIPPED 的伞形检查不算通过
这是整套分诊逻辑中最微妙、也最能体现 Electron CI 特点的一条规则(第 205 行至第 211 行):
当
--check指定的单个检查(默认即伞形检查)结果为SKIPPED时,脚本不将其视为通过,而是回退到 PR 的完整检查集重新分类,以找出真实状态。
原因在 Electron 的 CI 中很具体:当某个必需的上游 job 失败时,"GitHub Actions Completed" 伞形检查可能被跳过(skip),此时它的SKIPPED状态并不代表 CI 通过,只代表"这个聚合 job 没有真正跑完"。对应源码:
elif any($s[]; . == "SKIPPED") then # A SKIPPED umbrella is NOT a pass; fall back to the full check set. ( classify($allStates) // "green" )另外还有一个相关的边界规则:当指定检查在statusCheckRollup中根本不存在时(第 200 行至第 204 行),脚本区分两种情况——PR 一个检查都没有(length == 0)才判none;若 PR 上有其它检查、只是伞形检查尚未发出,则判pending。这条规则解释了为什么默认口径下,一个 PR 在自己的各项检查还在跑、而伞形检查还没贴出来时会被读作pending,等伞形检查贴出后才翻转为green/failing。
六、完整用法:参数、环境变量与输出格式
6.1 命令行示例(继承自 SKILL.md 全部示例)
在仓库根目录下运行:
# 绿色 PR(默认),扫描 electron/electron 最近 200 个 open PR .claude/skills/triage-prs/triage-prs.sh # 按不同状态过滤 .claude/skills/triage-prs/triage-prs.sh --status failing .claude/skills/triage-prs/triage-prs.sh --status none .claude/skills/triage-prs/triage-prs.sh --status pending # 分类所有被扫描的 PR 并逐一显示状态 .claude/skills/triage-prs/triage-prs.sh --status all # 少扫一些 PR 以加快结果 .claude/skills/triage-prs/triage-prs.sh --status failing --limit 50 # 指向其它仓库,或改变检查口径 .claude/skills/triage-prs/triage-prs.sh --repo electron/forge --limit 50 .claude/skills/triage-prs/triage-prs.sh --check "" # 评估全部检查 .claude/skills/triage-prs/triage-prs.sh --check "macos-x64 / build / build" # 定位到单个命名检查,例如找 faraday/cage 还在 pending 的 PR .claude/skills/triage-prs/triage-prs.sh --check "faraday/cage" --status pending .claude/skills/triage-prs/triage-prs.sh --check "faraday/cage" --status failing # Triage 看板 Status 列 .claude/skills/triage-prs/triage-prs.sh --no-triage # 隐藏 Status 列 .claude/skills/triage-prs/triage-prs.sh --triage-project "PR Triage" # 读取另一个看板6.2 参数与默认值对照
从 脚本第 48 行至第 53 行 可以直接确认全部默认值,且六个参数都支持同名环境变量作为默认值、再被命令行参数覆盖:
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
--status <s> | STATUS | green | 取值为green/failing/pending/none/all,非法值会报错退出 |
--check <name> | CHECK_NAME | GitHub Actions Completed | 只评估该命名检查;传空字符串则评估全部检查 |
--triage/--no-triage | TRIAGE | 1(开启) | 显示或隐藏 "PR Triage" 看板的 Status 列 |
--triage-project <n> | TRIAGE_PROJECT | PR Triage | 读取 Status 的看板名称 |
--repo <owner/name> | REPO | electron/electron | 目标仓库 |
--limit <n> | LIMIT | 200 | 扫描的 open PR 数量上限 |
--help的实现也很讲究:第 58 行至第 60 行 用awk动态提取脚本头部从 shebang 之后到第一个非注释行之间的注释块作为帮助文本,而不是写死行号范围,这样头部注释扩充后帮助文本自动保持同步。
6.3 输出格式
每个 PR 输出一行条目:图标、PR 编号的可点击超链接(OSC 8 终端超链接)、标题、各类标记,第二行给出裸 URL 以便纯文本终端使用(第 247 行至第 248 行):
✅ #52533 feat: support `restrictOwnAudio` constraint [PR Triage: Needs Review] https://github.com/electron/electron/pull/52533其中[new-pr]标记(当 PR 带new-pr标签时追加)的判定逻辑见 第 226 行至第 229 行:由于实际标签名以new-pr开头并带后缀(标签全名含尾部 emoji),脚本用前缀匹配startswith("new-pr")来识别,避免硬编码完整标签名而失配。
七、与 "PR Triage" 项目看板的联动
除 CI 状态外,脚本还会为每个 PR 标注它在 "PR Triage" GitHub 项目看板(ProjectV2)上的Status字段,例如[PR Triage: Needs Review]。实现上分两步:
- 拉取 Status 值(第 110 行至第 133 行):对每个 PR 发一条 GraphQL 查询
pullRequest.projectItems(first:20),取出每个projectItem所属项目的title和名为Status的字段值(ProjectV2ItemFieldSingleSelectValue),再用jq过滤出title == $TRIAGE_PROJECT的那一条。一个 PR 可能同时挂在多个项目看板上,因此按看板名精确匹配是必要的。 - 过滤 WIP(第 237 行至第 240 行):如果 Status 值包含
WIP子串,该 PR 被静默排除——它被视为"尚未准备好评审",不出现在结果里,也不计入总数。
这里有一个重要的副作用:--no-triage会跳过全部看板查询,此时 WIP PR会被包含在结果中(因为过滤依赖看板数据)。也就是说--no-triage的输出集合与默认输出集合在 WIP 维度上并不可比,这一点在汇总结果时必须向用户说明。
八、两个快捷入口:faraday 与 failing
SKILL.md 定义了面向维护者的两个高频快捷指令:
faraday——当用户只说 "faraday"(或 "faraday prs"、"pending faraday")时,运行:.claude/skills/triage-prs/triage-prs.sh --check "faraday/cage" --status pending列出
faraday/cage检查仍在 pending 的 open PR。faraday 是 Electron 使用的 PR 信任/审查机器人,其仓库内配置文件 faraday.yml 定义了 "trusted-bots"(如trop[bot]、electron-roller[bot]等回滚/回移机器人只需 1 个 maintainer 审批)与 "trusted-reviewers"(文档审查 bot 仅在改动全部位于docs/**时计数)。faraday/cage是它发出的 CI 检查,维护者常需要知道哪些 PR 还卡在 faraday 排队中。failing——当用户只说 "failing prs"(或 PR 分诊语境下的 "failing ci")时,运行:.claude/skills/triage-prs/triage-prs.sh --status failing列出检查失败的 open PR。
九、运行时长与 API 消耗:不要误判脚本挂起
SKILL.md 的 Runtime 一节给出了必须了解的运行特性:
- 每个 PR 最多触发两次
gh调用(一次gh pr view取statusCheckRollup/标题/URL/标签,一次 GraphQL 查看板 Status),且串行执行; - 因此在默认
--limit 200下,一次完整扫描需要数分钟并消耗数百次 API 请求; - 执行时应显式使用较长超时(10 分钟是安全值),或用更小的
--limit做快速局部扫描; - 不要假设脚本挂起——长时间无输出属于正常现象。
主循环结构(第 250 行至第 251 行)先用一条gh pr list一次性取回 open、非草稿 PR 的编号列表,再逐个gh pr view,这是 API 调用量的主要来源。
十、如何正确解读输出结果
这是 SKILL.md 中 "Interpreting results" 一节的全部要点,逐条对应源码行为:
- 两类 PR 在所有运行中被静默排除,且不计入总数,汇总时必须说明输出并非 open PR 全集:
- Draft PR:在
gh pr list阶段就被select(.isDraft | not)过滤(第 251 行); - 看板 Status 含
WIP的 PR:视为未准备好评审(第 237 行至第 240 行);注意--no-triage跳过看板查询,此时 WIP PR会被包含。
- Draft PR:在
- 带状态过滤(默认
green)时只列出分类匹配的 PR,其余被省略。 none表示该 PR真的一个检查都没有(什么都还没触发),这与pending(检查存在但未完成)有本质区别。- 由于默认评估口径是 "GitHub Actions Completed" 伞形检查,一个 PR 在其各项检查运行期间会被读作
pending,直到伞形检查发出状态才翻转为green/failing;只有零检查的 PR 才会是none。想按全部检查判断就用--check ""。 - 伞形检查
SKIPPED不视为通过(见第五节),脚本会回退到完整检查集找真实状态。
十一、汇总结果的报告规范
SKILL.md 的 "Reporting results" 一节规定了把脚本输出转述给使用者时的两条格式约定:
- 每个 PR 渲染成 Markdown 链接使其在聊天界面可点击,例如
[PR #52533](https://github.com/electron/electron/pull/52533) — feat: support restrictOwnAudio constraint。脚本输出中每条目的第二行就是该 PR 的 URL,直接取用即可; - 当 PR 带
new-pr标签(脚本输出[new-pr]标记)时,汇总中用纯文本[new-pr]表示,而不是 emoji。
十二、小结
triage-prs 技能是 Electron 仓库把 "PR 分诊" 这一重复性维护工作沉淀下来的典型范例:SKILL.md 声明触发词、状态语义与用法,triage-prs.sh 用gh+jq实现严格的四态分类,并与仓库自身的 CI 设计深度耦合——评估口径默认锚定 build.yml 中聚合十几个平台 job 的 "GitHub Actions Completed" 伞形检查,针对SKIPPED伞形检查回退全量检查、区分none与pending、静默排除 Draft/WIP PR 等规则,全部对应 Electron CI 的真实行为而非通用假设。理解这套实现,既能直接复用该脚本完成日常分诊,也为在其它大型 CI 矩阵仓库中设计类似的 "按检查状态批量分诊 PR" 工具提供了可参考的完整样本。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考