执行摘要
- 一句话:扩展 /ci retry 支持跨 commit 重试失败步骤
- 推荐动作:建议阅读该 PR,特别是
run_ci_command.py 中 list_failed_jobs 的游标分页实现和 run 函数中的新 head 分支逻辑,了解 CI 命令与流水线集成的模式(环境变量过滤、幂等性、跨构建元数据传递)。
功能与动机
将现有 /ci retry 命令扩展到跨 PR commits,避免每次新 commit 都重新运行所有 CI,仅重试之前失败的步骤,节省 CI 资源和时间。
实现拆解
- BuildkiteClient 扩展:在
.github/workflows/scripts/run_ci_command.py 中新增 _headers、_request_url 方法统一请求构建,新增 list_failed_jobs 方法通过游标分页获取失败/超时/过期作业列表,提取稳定的 step_key。
- list_builds 修改:允许
commit 参数为 None 以查询所有构建,并支持通过元数据筛选命令触发的构建(如 github-pr-command)。
- run 函数新分支:如果当前 PR head 没有 Buildkite 构建,则使用
select_latest_build 查找同 PR 的最近命令触发构建,然后调用 list_failed_jobs 获取失败作业,去重后设置环境变量 VLLM_CI_ONLY_STEP_KEYS,并调用 create_retry_build_payload 创建过滤构建。同时记录源构建和 commit 到元数据,并回复 PR 评论确认两个构建。
- 幂等性与安全:使用已有 comment 元数据避免重复处理;在分发前重新检查 head 防止竞态;当作业无稳定
step_key 或 CI 设置失败时 fail closed 并提示使用 /ci run。
- 测试覆盖:在
test_run_ci_command.py 中新增 5 个测试方法,验证过滤构建创建、去重、游标分页、拒绝不完整设置失败等场景,并扩展 FakeBuildkite 和 FakeTransport 以支持模拟。
关键文件:
.github/workflows/scripts/run_ci_command.py(模块 CI 命令;类别 infra;类型 infrastructure;符号 _headers, _request_url, list_failed_jobs, create_retry_build_payload): 核心实现,扩展 CI 命令逻辑以支持在新 commit 上重试失败步骤
.github/workflows/scripts/test_run_ci_command.py(模块 CI 测试;类别 test;类型 test-coverage;符号 test_ci_retry_creates_filtered_build_for_new_head, test_ci_retry_new_head_requires_stable_step_keys, test_ci_retry_new_head_rejects_incomplete_setup_failure, test_buildkite_list_builds_allows_query_on_builds_endpoint): 新增测试覆盖新 head 重试、去重、拒绝不完整设置失败等场景
关键符号:_headers, _request_url, list_failed_jobs, create_retry_build_payload, list_builds, run
关键源码片段
.github/workflows/scripts/run_ci_command.py
核心实现,扩展 CI 命令逻辑以支持在新 commit 上重试失败步骤
# 从 Buildkite API 获取构建中失败 / 超时 / 过期作业列表,支持游标分页
def list_failed_jobs(self, build_number: int) -> list[dict[str, Any]]:
number = urllib.parse.quote(str(build_number), safe="")
query = [
("state[]", "failed"), # 只获取失败状态
("state[]", "timed_out"), # 以及超时状态
("state[]", "expired"), # 和过期状态
("include_retried_jobs", "false"), # 不包含已重试的作业
("per_page", "100"), # 每页最多 100 条
]
url = f"{self.base_url}/{number}/jobs?{urllib.parse.urlencode(query)}"
jobs: list[dict[str, Any]] = []
while url: # 游标分页循环
response = self._request_url(url)
if not isinstance(response, Mapping):
raise ApiError(None, "Buildkite API returned an invalid job list.")
items = response.get("items")
links = response.get("links")
if not isinstance(items, list) or not isinstance(links, Mapping):
raise ApiError(None, "Buildkite API returned an invalid job list.")
jobs.extend(items) # 收集当前页作业
next_url = links.get("next")
if next_url is not None and not isinstance(next_url, str):
raise ApiError(None, "Buildkite API returned an invalid pagination URL.")
url = next_url # 指向下一页或 None
return jobs
评论区精华
本 PR 的审查讨论较少,主要决策已在 PR body 中阐明,包括与 #49849(仓库元数据控制面)和 #50238(临时同 commit 重测试)的区分,以及对 ci-infra#442(流水线步骤键过滤)的依赖。合并者 ywang96 直接批准。
- CI 重试命令扩展设计 (design): 采用环境变量过滤流水线方式,依赖 ci-infra#442 先合并;区分了更广泛的控制面方案。
风险与影响
- 风险:
- 依赖外部仓库:ci-infra#442 是流水线侧的前置条件,若未先合并,
VLLM_CI_ONLY_STEP_KEYS 环境变量将被流水线生成器忽略,导致重试运行全部步骤而非过滤步骤。
- API 异常处理:新增的
list_failed_jobs 和 _request_url 可能遇到 Buildkite API 返回格式异常(如无效分页 URL),当前通过 ApiError 抛出,可能导致重试失败且无直接 fallback。
- 竞态条件:在检查 head 和实际创建构建之间,PR 可能又有新 commit,此时创建的过滤构建基于过期作业列表。PR 中通过在 dispatch 前重新检查 head 来缓解,但仍有窗口。
- 幂等性依赖元数据:依赖 comment ID 元数据去重,若元数据丢失(如 Buildkite 清理)可能导致重复重试。
- 影响:影响范围仅限于 CI 流程和开发体验。开发者可以使用
/ci retry 在新 commit 上仅重试之前失败的步骤,从而节省 CI 资源并加快反馈循环。不涉及模型推理、用户请求或系统稳定性。团队需确保 ci-infra#442 先合并,并在发布后通知贡献者新行为。
- 风险标记:依赖外部仓库合并, 幂等性依赖元数据, 新代码覆盖 CI 命令路径
关联脉络
参与讨论