Prhub

#51732 [CI] Add /ci cancel command

原始 PR 作者 khluu 合并时间 2026-08-11 05:55 文件变更 4 提交数 1 评论 0 代码增减 +151 / -6

执行摘要

PR 评论新增 /ci cancel 命令,可取消 Buildkite 构建

PR body 中提到:"Reviewers often have enough signal after an early job failure and should be able to stop the remaining branch build instead of consuming more CI capacity." 即在早期 job 失败后,reviewer 已经获得足够信号,应当能够停止剩余的构建,避免继续消耗 CI 容量。此前 CI 机器人只支持 /ci run/ci retry,缺少主动取消的能力,本 PR 补齐了这一缺口。

值得快速阅读。该 PR 是一个小而完整的 CI 命令范式:常量定义、精确解析、权限复用、API 封装与测试覆盖齐备,适合作为后续扩展其他 /ci 子命令(如 /ci restart/ci status)的模板。真正值得借鉴的设计是 list_builds 的参数化过滤与 is_build_for_pr 的归属校验组合,这能有效防止误操作。

讨论亮点

该 PR 没有产生实质性的 review 讨论,review_comments_count = 0。claude[bot] 仅自动提示本仓库配置了手动 code review 机制;ywang96 直接给出 APPROVED,无附言。从现有材料看,设计上唯一的隐性取舍是:取消构建前同时校验 branchis_build_for_pr,以防范误取消同一分支的其他构建,这一点已在测试用例中覆盖。

实现拆解

  1. 命令注册与解析:在 .github/workflows/scripts/run_ci_command.py 中新增常量 COMMAND_CANCEL_CI = "/ci cancel"CANCELABLE_BUILD_STATES = ("scheduled", "running", "failing")parse_command 的精确匹配集合加入 COMMAND_CANCEL_CI,并确保 /ci cancel please 这类带后缀的评论不会被误识别。
  2. BuildkiteClient 扩展list_builds 新增 branchstates 参数,分别拼接 branch=<名称>state[]=<状态> 查询项;新增 cancel_build 方法,对构建号做 URL 编码后调用 PUT /{number}/cancel 端点,与已有的 retry_failed_jobs 保持一致的 REST 风格。
  3. 命令处理主流程:新增 handle_cancel_ci 函数,先按 PR 的 head 分支列出可取消构建,再通过 is_build_for_pr 双重校验构建归属(兼容 pull_request.idmeta_data.github-pr-number 两种元数据来源),逐个发起取消请求,最后以链接形式反馈已取消的构建编号;没有匹配构建时返回 no-op 提示。run 主流程中,命令分支从 else 改为显式区分 COMMAND_RETRY_FAILEDhandle_cancel_ci
  4. 工作流与文案配套.github/workflows/run-ci-command.ymlif 条件加入 /ci cancel,使该评论能触发工作流;.github/workflows/new_pr_bot.yml 的欢迎消息补充 /ci cancel 介绍;authorize 的提示文案从“can run CI”改为更通用的“can use CI commands”,以涵盖取消命令。
  5. 测试配套test_run_ci_command.py 新增 3 个测试——取消当前 PR 分支的活跃构建(含分支、PR 归属、状态过滤的边界用例)、无活跃构建时 no-op、以及取消端点请求的 URL 与 HTTP method 断言;同时为 FakeBuildkite 补充 cancel_buildlist_buildsbranchstates 参数支持。
文件 模块 状态 重要度
.github/workflows/scripts/run_ci_command.py 命令脚本 modified 6.12
.github/workflows/scripts/test_run_ci_command.py 命令脚本 modified 6.8
.github/workflows/run-ci-command.yml 工作流配置 modified 3.05
.github/workflows/new_pr_bot.yml 机器人通知 modified 2.58

关键符号

handle_cancel_ci cancel_build list_builds parse_command

关键源码片段

.github/workflows/scripts/run_ci_command.py core-logic

核心实现文件:新增 `/ci cancel` 命令常量、可取消状态集合、`cancel_build` API 方法与 `handle_cancel_ci` 处理函数,并扩展 `list_builds` 支持分支与状态过滤;命令分发逻辑也从单一的 else 分支改为显式路由。

# 可取消的构建状态集合:scheduled(排队中)、running(运行中)、failing(失败中)
CANCELABLE_BUILD_STATES = ("scheduled", "running", "failing")def cancel_build(self, build_number: int) -> dict[str, Any]:
    # 调用 Buildkite Builds API 的取消端点:PUT /{number}/cancel,构建号先做 URL 编码
    number = urllib.parse.quote(str(build_number), safe="")
    return self._request(method="PUT", path=f"/{number}/cancel")def handle_cancel_ci(
    *,
    buildkite: BuildkiteClient,
    pr: Mapping[str, Any],
) -> str:
    # 只针对当前 PR 的 head 分支查询可取消的构建
    branch = pr["head"]["ref"]
    builds = buildkite.list_builds(
        None,
        branch=branch,
        states=CANCELABLE_BUILD_STATES,
    )
    # 双重归属校验:既匹配分支,又通过 `is_build_for_pr` 确认构建属于当前 PR,
    # 避免误取消同一分支上其他 PR 或非 PR 构建
    cancelable_builds = [
        build
        for build in builds
        if build.get("branch") == branch
        and is_build_for_pr(build, pr["number"])
        and build.get("state") in CANCELABLE_BUILD_STATES
    ]
    # 没有匹配的活跃构建时返回 no-op 提示,不产生额外 API 调用
    if not cancelable_builds:
        return f"No cancelable CI build is running for branch `{branch}`."
​
    for build in cancelable_builds:
        buildkite.cancel_build(build["number"])
​
    # 以链接形式反馈已请求取消的构建,方便 reviewer 直接跳转查看
    links = ", ".join(
        f"[#{build['number']}]({build['web_url']})" for build in cancelable_builds
    )
    count = len(cancelable_builds)
    noun = "build" if count == 1 else "builds"
    return f"Requested cancellation of {count} CI {noun} for `{branch}`: {links}."

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 误取消风险:取消逻辑依赖 build.get("branch") == branchis_build_for_pr(...) 双重校验,但若 Buildkite 返回的构建元数据不完整(例如既无 pull_request 也无 meta_data.github-pr-number),则该构建会被静默跳过,属于偏保守的取舍;反之若 is_build_for_pr 存在边界漏洞,则可能误取消。现有测试覆盖了 pull_request.idmeta_data 两种路径,但未覆盖两者皆缺的异常场景。
  2. 分页未处理list_builds 未处理 Buildkite 列表分页,若某个 PR 分支历史构建非常多,可能只取到第一页构建;考虑到实际场景下活跃构建数量有限,风险中低,但值得留意。
  3. 取消竞态:从列出构建到发出取消请求之间存在时间窗口,若构建恰好已结束,PUT /{number}/cancel 可能返回错误;handle_cancel_ci 对单个构建取消失败未做局部容错,异常会向上抛出让整个评论处理进入 ❌ 回复路径,影响用户反馈体验。
  4. 权限模型复用/ci cancel 复用既有 authorize 流程,未授权用户无法真正取消构建,风险可控;但欢迎消息的文案变更可能影响依赖该文本的外部集成。

影响范围集中在 CI 机器人工具链,不涉及模型推理、服务端行为或用户 API。受益方主要是维护者与获得批准后的 PR 作者:可在早期失败后立即停止剩余构建,节省 CI 容量与排队时间。所有提交 PR 的用户会看到欢迎消息中的新命令说明;Buildkite 侧会收到取消请求,但调用频率低,负载影响可忽略。对团队而言,CI 容量管理更加精细,减少因“早失败但仍跑完”导致的资源浪费。

误取消防护依赖双重校验 构建列表未处理分页 取消请求存在竞态 单构建取消失败无局部容错

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论