diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd976e031..2c91ab4ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,13 @@ permissions: # queued push but the newest during a merge burst, and most main commits never # got a verdict. Merge-group refs (gh-readonly-queue/...) are unique per queue # entry already. The nightly keeps its own group so it never queues behind (or -# cancels) a push. +# cancels) a push. Draft PR runs get their own group too: a push followed at +# once by `gh pr ready` can deliver the push's `synchronize` (payload still +# draft) after `ready_for_review`. In a shared group that late draft run +# cancelled the real run, skipped every job, and left ci-ok green on a head +# nothing had tested. concurrency: - group: ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }} + group: ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}${{ github.event.pull_request.draft && '-draft' || '' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} # Linux jobs run on Depot's runners (depot-ubuntu-*-4: 4 vCPU, like GitHub's diff --git a/scripts/tests/test_ci_scheduling.py b/scripts/tests/test_ci_scheduling.py index e49271e89..ae0cb7951 100644 --- a/scripts/tests/test_ci_scheduling.py +++ b/scripts/tests/test_ci_scheduling.py @@ -203,6 +203,13 @@ def test_unchanged_pr_merge_skips_only_what_the_queue_re_tests(self): self.assertIn("if: github.event_name == 'pull_request' && github.base_ref == 'main'" " && github.head_ref != 'release/v5-prerelease'", owner) + def test_late_draft_run_cannot_cancel_the_ready_run(self): + # A push's draft-payload `synchronize` can land after `ready_for_review` + # on the same head; sharing a group, it cancelled the real run and left + # ci-ok green with every job skipped. + group = re.search(r"^concurrency:\n group: (.*)$", TEXT, re.M)[1] + self.assertIn("github.event.pull_request.draft && '-draft'", group) + def test_row_reader_preserves_both_os_siblings(self): jobs = reader.jobs("""jobs: example: