# PR #50318 完整报告

- 仓库：`vllm-project/vllm`
- 标题：[CI] Retry failed steps on new PR commits
- 合并时间：2026-07-30 14:54
- 原文链接：http://prhub.com.cn/vllm-project/vllm/pull/50318

---

# 执行摘要

- 一句话：扩展 /ci retry 支持跨 commit 重试失败步骤
- 推荐动作：建议阅读该 PR，特别是 `run_ci_command.py` 中 `list_failed_jobs` 的游标分页实现和 `run` 函数中的新 head 分支逻辑，了解 CI 命令与流水线集成的模式（环境变量过滤、幂等性、跨构建元数据传递）。

# 功能与动机

将现有 /ci retry 命令扩展到跨 PR commits，避免每次新 commit 都重新运行所有 CI，仅重试之前失败的步骤，节省 CI 资源和时间。

# 实现拆解

1. **BuildkiteClient 扩展**：在 `.github/workflows/scripts/run_ci_command.py` 中新增 `_headers`、`_request_url` 方法统一请求构建，新增 `list_failed_jobs` 方法通过游标分页获取失败 / 超时 / 过期作业列表，提取稳定的 `step_key`。
2. **list_builds 修改**：允许 `commit` 参数为 `None` 以查询所有构建，并支持通过元数据筛选命令触发的构建（如 `github-pr-command`）。
3. **run 函数新分支**：如果当前 PR head 没有 Buildkite 构建，则使用 `select_latest_build` 查找同 PR 的最近命令触发构建，然后调用 `list_failed_jobs` 获取失败作业，去重后设置环境变量 `VLLM_CI_ONLY_STEP_KEYS`，并调用 `create_retry_build_payload` 创建过滤构建。同时记录源构建和 commit 到元数据，并回复 PR 评论确认两个构建。
4. **幂等性与安全**：使用已有 comment 元数据避免重复处理；在分发前重新检查 head 防止竞态；当作业无稳定 `step_key` 或 CI 设置失败时 fail closed 并提示使用 `/ci run`。
5. **测试覆盖**：在 `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 上重试失败步骤

```python
# 从 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 命令路径

# 关联脉络

- 暂无明显关联 PR