首页
/ gemini-cli 的 Issue 与 PR 自动化分诊:从 Bot 触发规则到源码级实现解析

gemini-cli 的 Issue 与 PR 自动化分诊:从 Bot 触发规则到源码级实现解析

2026-09-06 12:45:56作者:伍希望

本文基于 gemini-cli 仓库中的 自动化与分诊流程文档 展开,系统拆解该项目用于管理 Issue 与 Pull Request 的七条自动化工作流:谁触发、何时运行、做了什么、以及贡献者应该配合做什么。读完本文,你不仅能理解该项目机器人系统的完整运作方式(包括标签体系、Gemini 模型分诊提示词、防重复注释策略等),还能直接对照仓库中每个工作流的 YAML 定义与配套脚本,将其作为你自己项目 CI/分诊自动化的设计参考。

核心原则:Issue 描述"是什么",PR 描述"怎么做"

整套自动化建立在一个基本约定上:几乎每个 PR 都应关联一个对应的 Issue。Issue 描述"是什么"和"为什么"(缺陷或功能需求),PR 描述"怎么做"(具体实现)。这种分离让团队可以追踪工作量、排定优先级,并保留清晰的历史上下文,仓库里的标签同步、链接检查等自动化都围绕这一原则构建。

注意:带有 "🔒Maintainers only" 标签的 Issue 是保留给项目维护者的,不会接受与之相关的 PR。

这一约定不是空话,仓库中有多处证据支撑:

  • PR 模板 pull_request_template.md 中的 "Related Issues" 一节明确要求使用关键字自动关闭 Issue(Closes #123Fixes #456),如果只是部分相关则直接引用编号(Related to #123);
  • 定时 PR 分诊脚本 pr-triage.sh 会扫描 PR 的 closingIssuesReferences,甚至用正则 (^|[^a-zA-Z0-9])#(?<num>[0-9]+) 从 PR 正文中兜底提取 #编号(见该脚本第 141 行附近),找不到任何关联 Issue 的非 Draft PR 会被打上 status/need-issue 标签。

标签体系:分诊输出的"结构化语言"

理解各工作流之前,先了解它们共同操作的一组标签前缀。从 定时 Issue 分诊工作流 的检索语句中可以看到完整标签空间:

前缀 含义 示例取值
area/* 功能领域 area/agentarea/corearea/enterprisearea/non-interactivearea/securityarea/platformarea/extensionsarea/documentationarea/unknown
kind/* Issue 类型 kind/bugkind/enhancementkind/customer-issuekind/question
priority/* 优先级(P0 严重到 P3 低) priority/p0priority/p1priority/p2priority/p3priority/unknown
effort/* 工作量估计 effort/smalleffort/mediumeffort/large
status/* 状态 status/need-triagestatus/need-informationstatus/need-issuestatus/bot-triagedstatus/manual-triage
size/* PR 改动规模 size/XSsize/XL

其中 area/*kind/*priority/* 三类都遵循"每个 Issue 有且只有一个"的约束,后面的分诊工作流正是围绕"补齐缺失项、消除冲突项"运转的。

工作流一:开 Issue 时的即时分诊(Automated Issue Triage)

  • 工作流文件gemini-automated-issue-triage.yml
  • 触发时机:Issue 创建或重新打开(issues: opened/reopened);也支持 workflow_dispatch 手动传入 Issue 编号,以及被其他工作流以 workflow_call 复用。
  • 运行条件(可从文件头部 if 表达式读出):仓库必须是官方主仓库;Issue 尚未带有 area/ 标签(避免重复分诊);issue_comment 触发的重分诊要求评论包含 @gemini-cli /triage 且评论者为 OWNER/MEMBER/COLLABORATOR。

它做的事情,是把"给 Issue 打标签"这件事交给 Gemini 模型完成,流程如下:

  1. 准备上下文:将 Issue 标题与正文写入工作区的 issue_context.md 文件("Prepare Issue Data" 步骤)。

  2. 列出可用标签:通过 GitHub API 拉取仓库标签,并硬编码一个 allowedLabels 白名单(9 个 area/* 标签,见工作流文件第 118–128 行),只允许模型在这些标签中做选择。

  3. 运行 Gemini CLI 分析:使用 google-github-actions/run-gemini-cli 动作,提示词要求模型:

    • read_file 读取 issue_context.md
    • 根据内嵌的 "Reference 1: Area Definitions"(对每个 area/* 的定义与典型 Issue 举例)选出恰好一个 area/ 标签;
    • 无法确定时回退到 area/unknown
    • 只输出 JSON:{"labels_to_set": ["area/core"]}

    这里的安全约束值得注意:运行环境显式设置 GITHUB_TOKEN: ''(注释写明"此处不传任何认证 token,因为这是在不可信输入上运行"),并通过 settings 把可用工具限制为 run_shell_command(echo)read_filemaxSessionTurns 限制为 25。也就是说,分诊 Agent 是一个只读、无凭证、工具白名单收窄的受限实例。

  4. 容错解析模型输出apply-issue-labels 相关脚本先直接 JSON.parse 模型输出;失败后尝试从 Markdown 代码块(json ... )中提取;再失败则用正则 (\{[\s\S]*"labels_to_set"[\s\S]*\}) 在含调试日志的输出里定位 JSON 对象。解析后还会验证"必须恰好一个标签",先移除旧的冲突 area/* 标签再添加新标签(见工作流文件第 296–375 行)。

  5. 失败兜底:如果 Gemini 分析步骤失败,会在 Issue 下发布评论提示查看 Action 运行日志("Post Issue Analysis Failure Comment" 步骤)。

贡献者该做什么:尽量完整填写 Issue 模板(仓库提供了 bug_report.ymlfeature_request.yml 等模板);如果被打上 status/need-information,在评论中补充缺失的日志或复现步骤。

工作流二:PR 的持续集成(CI)

  • 工作流文件ci.yml
  • 触发时机:每次推送到 PR(pull_request,目标分支 mainrelease/**)、直接推送这些分支(push)、合并队列(merge_group)以及手动触发。

从源码看,CI 由七个并行 Job 组成,最后由一个聚合 Job 汇总判定:

Job 做什么
lint 依据 .nvmrc 装 Node 后依次执行:ESLint、actionlint、shellcheck、yamllint、npm run build + npm run typecheck、Prettier、设置文档一致性检查(npm run docs:settings -- --check)、敏感词检查、GitHub Actions 版本钉死检查(--check-github-actions-pinning)。所有 linter 统一由 scripts/lint.js 驱动
link_checker 用 lychee 检查仓库内所有 .md 文件中的链接有效性
test_linux 在 Node 20.x/22.x/24.x × 两个测试分片(cli / others)矩阵上运行 npm run test:ci,随后 npm run bundle 打包并做 node ./bundle/gemini.js --versionnpx 安装冒烟测试;非 fork 仓库用 dorny/test-reporter 发布 JUnit 报告,fork 仓库则上传产物
test_mac 与 Linux 相同的矩阵,另上传 coverage 报告
codeql 对 JavaScript 做 CodeQL 静态安全分析
bundle_size preactjs/compressed-size-action 监控 bundle/** 产物体积变化(阈值 1000 字节)
test_windows 在 16 核 Windows 自定义 Runner 上跑"慢测试",60 分钟超时

几个从配置中能读出的工程细节:

  • 每个仓库 Job 都先经过 merge_queue_skipper,用于合并队列场景下避免重复跑 CI;
  • Linux 测试前会安装 bubblewrap(沙箱依赖)并放宽 AppArmor 限制(Ubuntu 24.04+ 需要);
  • 测试环境变量包含 GEMINI_CLI_TRUST_WORKSPACE: true,即在 CI 中预信任工作区;
  • 测试按 workspace 分片:cli 分片只跑 @google/gemini-cliothers 分片跑 @google/gemini-cli-core@google/gemini-cli-a2a-servergemini-cli-vscode-ide-companion@google/gemini-cli-test-utils 以及 npm run test:scripts,保证分片互不重叠;
  • 最终 ci Job 逐一检查七个 Job 的 needs.*.result,任何一个非 success/skipped 即整体失败。

关于"Post Coverage Comment":文档提到测试全部通过后机器人会在 PR 上发布覆盖度总结评论。该能力由 post-coverage-comment 复合 Action 实现:用 jq 从 CLI 与 Core 两个包的 coverage-summary.json 中取出 Lines/Statements/Functions/Branches 百分比,拼成 Markdown 表格(附可折叠的完整文本报告),并通过 thollander/actions-comment-pull-requestcode-coverage-summary 标签写入或更新评论——同一标签下重复运行是"更新"而非"新发",避免刷屏。

贡献者该做什么:确保所有检查通过(绿色对勾);出现红色叉号时点击 "Details" 查看日志、定位问题并推送修复。

工作流三:PR 的持续审计与标签同步(PR Auditing and Label Sync)

  • 工作流文件gemini-scheduled-pr-triage.yml
  • 触发时机cron: '*/15 * * * *',即每 15 分钟对所有打开的 PR 审计一次,也可手动触发;整个 Job 限时 15 分钟。

核心逻辑全部在 Bash 脚本 pr-triage.sh 中(约 183 行),可以逐段读懂:

  1. 批量拉取gh pr list --state open --limit 1000,一次性取回每个 PR 的 numberisDraftclosingIssuesReferencesbodylabels 五个字段,再用 jq 压缩成 TSV 逐行处理。关联 Issue 的识别优先用 GraphQL 的 closingIssuesReferences,兜底才用正则从正文提取 #数字
  2. 无关联 Issue 的处理
    • 非 Draft PR:添加 status/need-issue 标签(如尚未有),并把 PR 编号记入 prs_needing_comment 输出,供后续步骤通知;
    • Draft PR:相反地移除 status/need-issue——草稿阶段不催关联 Issue。
  3. 有关联 Issue 的标签同步:先移除 status/need-issue;然后拉取 Issue 标签,但只关心 area/*priority/*help wanted🔒 maintainer only 这几类(脚本第 43 行的 grep -x -E 过滤),把 PR 上缺失的补齐。Issue 标签有进程内缓存(兼容 Bash 3.2 的扁平字符串缓存)避免重复请求 API。
  4. 原子更新:最终用一条 gh pr edit --add-label ... --remove-label ... 完成增删。

一个微妙的设计点:同步方向是 Issue → PR 单向补齐(只添加、只移除 status/need-issue),不会把 PR 上多余的 area/*/priority/* 删掉,因此不会误伤人工调整。

贡献者该做什么:永远把 PR 关联到 Issue(在描述中写 Resolves #<issue-number> 之类),这是整个流程中最重要的一步——它会保证 PR 被正确归类并顺畅地走评审。

工作流四:Issue 的定时兜底分诊(Scheduled Issue Triage)

  • 工作流文件gemini-scheduled-issue-triage.yml
  • 触发时机cron: '0 * * * *',每小时对全部打开的 Issue 运行一次(Job 限时 60 分钟),也支持手动触发。

它是即时分诊的"安全网",但能力比即时版更强。从源码看,一次定时运行会依次做:

  1. 同步 Issue 类型sync-issue-types.cjs 通过 GraphQL 查询最近更新的 50 个打开 Issue,对尚未设置 GitHub Issue Type 的,依据 kind/bugkind/feature/kind/enhancement 标签回填 Bug/Feature 类型(运行前后各同步一次)。
  2. 找出冲突 Issue:一次拉取最多 2000 个打开 Issue,筛出带多个 area/多个 priority/ 标签的(上限 50 个)——违反"每类唯一"约束。
  3. 找出漏网 Issue:分别检索缺失 area/*、缺失 kind/*、缺失 priority/* 的 Issue(各限 50 个),合并去重;另外为 area/corearea/extensionsarea/sitearea/non-interactive 中的 Issue 单独检查缺失的 effort/* 标签(限 20 个)。
  4. 标准分诊(Gemini):与即时分诊相同的方式运行 run-gemini-cli(模型 gemini-3-flash-preview,工具白名单只有 echoread_file,不传 GITHUB_TOKEN),但提示词是一整套标签策略:
    • 已有唯一 area/、唯一 kind/、唯一 priority/不动;缺失或有冲突时必须恰好选一个,冲突项写入 labels_to_remove
    • P0 不能自动打:判定为 P0 时改打 priority/p1 + status/manual-triage,留给人工升级;
    • kind/bug 检查正文中的 CLI 版本:若比当前版本(运行时从 package.json 读取)旧超过 6 个 minor 版本,打 status/need-information 并留言请用户在最新版上复测;信息不足(如缺复现步骤、缺版本号)同样打 status/need-information
    • 输出 JSON 数组,每个对象含 issue_numberlabels_to_addlabels_to_remove 与面向用户措辞的 explanation(明确规定解释中不得暴露标签机制)。
  5. 工作量分诊(Effort,Gemini):这是定时分诊独有的深度环节。针对缺 effort/* 标签的 Issue,提示词要求模型扮演"资深软件架构师",必须grep_searchglobread_file 实际搜索代码库定位涉及的文件与组件后再评级,并输出必须引用具体文件路径、禁止"likely"式猜测的 effort_analysis。分级标准内嵌在提示词中:
    • effort/small(≤1 天):schema 更新、单文件逻辑修复、UI 微调、文案修改;
    • effort/medium(2–3 天):React/Ink 状态管理调试、异步流与 IDE 伴生扩展问题、跨包(packages/clipackages/core)重构;
    • effort/large(3+ 天):node-pty/信号等跨平台复杂度、Scheduler 与 A2A/MCP 协议级重构、大规模性能内存问题;
    • 并特别规定:间歇性、难复现、平台相关的缺陷不得评为 effort/small
  6. 应用与清理:由 apply-issue-labels.cjs 统一应用标签;随后 cleanup-triage-labels.cjs 清理互斥的 status/* 组合(如同时带 status/bot-triagedstatus/need-triage 的 Issue)。

贡献者该做什么:通常什么都不用做——这正是"兜底"工作流,确保即使即时分诊失败,每个 Issue 最终也会被归类。

工作流五:自动移除失联的 Issue 处理人(Unassign Inactive Assignees)

  • 工作流文件unassign-inactive-assignees.yml
  • 触发时机:每天 09:00 UTC(cron: '0 9 * * *'),也可手动触发并传 dry_run: true 做无副作用演练。

目的:让 help wanted Issue 的处理权保持在流动状态。完整算法(内嵌在该工作流的 github-script 步骤中,约 250 行):

  1. 找出所有带 help wanted 标签且至少有一个 assignee 的打开 Issue;
  2. 豁免特权用户:三个组织团队(gemini-cli-maintainersgemini-cli-askmode-approversgemini-cli-docs)成员、仓库协作者中权限为 admin/maintain/write/triage 者、以及 googlers/google 组织成员,全部跳过——他们永远不会被自动移除;
  3. 对每个普通 assignee 读取 Issue 时间线(assigned/unassigned 事件确定精确的分配时刻,找不到事件则回退到 Issue 创建时间);
  4. 从时间线的 cross-referenced 事件中提取所有关联 PR,逐个拉取详情验证"就绪"状态:open 且非 Draft,或已 merged;Draft PR 明确不算数(工作流头部注释解释了原因——防止贡献者用一个空 Draft PR 刷掉检查);
  5. 若分配超过 7 天GRACE_PERIOD_DAYS = 7)且无合格 PR,则调用 removeAssignees 并留言,说明原因、如何重新认领(评论 /assign)以及如何避免再次发生(7 天内开出含 Fixes #<issue-number> 的非 Draft PR)。

贡献者该做什么

  • 被分配后 7 天内开一个真正可评审的 PR(非 Draft),并在描述中写 Fixes #<issue-number>;Draft PR 不满足条件;
  • 若被误移除,评论 /assign 重新认领;
  • 若放弃处理,评论 /unassign 释放给其他人。

工作流六:按改动规模自动打 PR 标签(PR Size Labeler)

  • 工作流文件pr-size-labeler.yml
  • 触发时机:PR 创建、同步(推送新提交)、重新打开(pull_request_target,注意它运行在主仓库权限下),也可用 workflow_dispatch 手动指定 PR 编号。

实现是一个纯 gh CLI 脚本,逻辑清晰可逐行对照:

  1. 自愈标签:先 gh label create 确保五个 size/* 标签存在(带规定颜色与描述),失败则忽略;
  2. 单次 API 取数gh pr view --json additions,deletions,changedFiles,labels 一次拿全,TOTAL = additions + deletions
  3. 计算标签(边界与文档一致):
标签 改动行数
size/XS < 10
size/S 10–49
size/M 50–249
size/L 250–999
size/XL ≥ 1000
  1. 原子更新:对比现有标签,只把"新增正确标签 + 移除过时标签"合并成一条 gh pr edit 调用;
  2. 防刷屏评论:构造形如 📊 PR Size: **size/M** … 的评论后,先在 Issue 评论区检索由 github-actions[bot] 发布且以 📊 PR Size: 开头的旧评论——找到就 PATCH 原地更新,找不到才新建。这样无论推多少轮提交,PR 时间线上永远只有一条尺寸说明。

贡献者该做什么:无需任何操作,标签与评论会随推送自动更新。

工作流七:发布自动化(Release Automation)

  • 工作流文件release-manual.yml
  • 触发时机:官方 patch/minor 版本通过 workflow_dispatch 手动触发;nightly 版本由定时任务(仓库另有 release-nightly.yml)触发。

手动发布工作流的 workflow_dispatch 输入本身就是一套发布参数表:

输入 说明
version 要发布的版本号(带 v 前缀的合法 semver,如 v0.1.11
ref 发布所用的分支、tag 或 SHA
npm_channel 发布渠道:dev / preview / nightly / latest(默认 latest
dry_run 干跑模式(默认 true):不创建分支、不发布 npm 包、不创建 GitHub Release
force_skip_tests 是否跳过测试步骤(默认不跳过,正式版本应跑测试)
skip_github_release 是否跳过创建 GitHub Release(仅 prod 环境有意义)
environment prod / dev

执行链:先通过可复用工作流构建 macOS 二进制,再在 release 子目录检出目标 ref、npm ci、下载二进制、计算 PREVIOUS_TAGgit describe --tags --abbrev=0)、可选跑测试,最后调用 publish-release 复合 Action 完成版本号提升、发布到 npm 并创建带生成说明的 GitHub Release。若正式发布(dry_run == false)中途失败,会自动创建一个带 release-failure,priority/p0 标签的 Issue 并附上运行链接——发布失败本身也被纳入了 Issue 分诊体系。

贡献者该做什么:不需要参与发布流程;PR 合入 main 后,其变更会进入下一次 nightly 发布。

小结:这套自动化体系的设计要点

把七条工作流放在一起看,可以提炼出几个可复用的模式:

  1. 模型做判断,脚本做执行:Gemini 只负责输出结构化 JSON(选哪个标签、为什么),真正调用 GitHub API 增删标签的始终是确定性脚本,且对模型输出做多级容错解析与数量校验;
  2. 最小权限 + 不可信输入隔离:在用户可控的 Issue 正文上运行的 Gemini 实例不持有任何 GitHub token,工具白名单收窄到只读;
  3. 幂等与防刷屏:评论一律"找到就更新"(覆盖率评论按 tag、尺寸评论按前缀匹配),标签操作先对比再批量原子提交,定时任务只处理"缺失或冲突"的增量;
  4. 分层兜底:即时分诊(秒级)→ 每小时定时分诊(补齐漏网 + 冲突治理 + 工作量评估)→ 每天清理失联 assignee,层层递进保证仓库状态最终一致;
  5. 人机边界清晰:P0 永远留给 status/manual-triage 人工升级,特权用户永远豁免自动 unassign,自动化只处理机械性环节。

对希望为自己的开源仓库搭建类似体系的开发者而言,本仓库的 docs/issue-and-pr-automation.md 是"给用户看的说明",而 .github/workflows/.github/scripts/ 目录则是"可直接抄的参考实现"——从触发器设计、标签约束策略到防重复注释的小技巧,都能从中找到答案。

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