Electron PR 分诊工具 triage-prs 详解:基于 gh CLI 按 CI 状态分类 open PR 的完整实践
2026/9/6 20:55:08 网站建设 项目流程

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-prsdescription字段让 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 行 会显式检查并给出报错):

  1. 安装并认证 GitHub CLI(gh auth login)。gh是脚本访问 GitHub 的唯一通道。
  2. 安装jq并保证在PATH中。脚本用jq解析gh返回的 JSON(状态汇总、标题、URL、标签、看板字段)。
  3. (可选)如需展示 "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 种失败态(包括STALESTARTUP_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 结果为failurecancelledexit 1if: 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>STATUSgreen取值为green/failing/pending/none/all,非法值会报错退出
--check <name>CHECK_NAMEGitHub Actions Completed只评估该命名检查;传空字符串则评估全部检查
--triage/--no-triageTRIAGE1(开启)显示或隐藏 "PR Triage" 看板的 Status 列
--triage-project <n>TRIAGE_PROJECTPR Triage读取 Status 的看板名称
--repo <owner/name>REPOelectron/electron目标仓库
--limit <n>LIMIT200扫描的 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]。实现上分两步:

  1. 拉取 Status 值(第 110 行至第 133 行):对每个 PR 发一条 GraphQL 查询pullRequest.projectItems(first:20),取出每个projectItem所属项目的title和名为Status的字段值(ProjectV2ItemFieldSingleSelectValue),再用jq过滤出title == $TRIAGE_PROJECT的那一条。一个 PR 可能同时挂在多个项目看板上,因此按看板名精确匹配是必要的。
  2. 过滤 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 viewstatusCheckRollup/标题/URL/标签,一次 GraphQL 查看板 Status),且串行执行;
  • 因此在默认--limit 200下,一次完整扫描需要数分钟并消耗数百次 API 请求
  • 执行时应显式使用较长超时(10 分钟是安全值),或用更小的--limit做快速局部扫描;
  • 不要假设脚本挂起——长时间无输出属于正常现象。

主循环结构(第 250 行至第 251 行)先用一条gh pr list一次性取回 open、非草稿 PR 的编号列表,再逐个gh pr view,这是 API 调用量的主要来源。

十、如何正确解读输出结果

这是 SKILL.md 中 "Interpreting results" 一节的全部要点,逐条对应源码行为:

  1. 两类 PR 在所有运行中被静默排除,且不计入总数,汇总时必须说明输出并非 open PR 全集:
    • Draft PR:在gh pr list阶段就被select(.isDraft | not)过滤(第 251 行);
    • 看板 Status 含WIP的 PR:视为未准备好评审(第 237 行至第 240 行);注意--no-triage跳过看板查询,此时 WIP PR被包含。
  2. 带状态过滤(默认green)时只列出分类匹配的 PR,其余被省略。
  3. none表示该 PR真的一个检查都没有(什么都还没触发),这与pending(检查存在但未完成)有本质区别。
  4. 由于默认评估口径是 "GitHub Actions Completed" 伞形检查,一个 PR 在其各项检查运行期间会被读作pending,直到伞形检查发出状态才翻转为green/failing;只有零检查的 PR 才会是none。想按全部检查判断就用--check ""
  5. 伞形检查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伞形检查回退全量检查、区分nonepending、静默排除 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询