From 7966b2059ce445077623864e100df227d470f169 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Fri, 2 Oct 2026 13:56:27 -0700 Subject: [PATCH] ci: stop the skipped PR-run mirror from satisfying Required PR Check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On an in-repo feature PR, the `pull_request` run skips the `required-check` job because the push run owns the status. A skipped job still posts a check run under its name, and GitHub treats a skipped required check as satisfied. The push run's mirror is only created once `ci` finishes, so for the whole test window the only `✅ Required PR Check` on the head SHA was the skipped one, and the PR could merge while tests were still running. Give the job a conditional name so the skipped path posts under a different name and the required check stays pending until the push run reports. Synced from CLDMV/.github#346. --- .github/workflows/ci.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ebf523..49b6c91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -304,7 +304,21 @@ jobs: # automatically — no `pull_request` round-trip needed for non-fork # non-release PRs. required-check: - name: ✅ Required PR Check + # The name is conditional on purpose. On an in-repo feature PR the + # `pull_request` run skips this job (the push run owns the status), and + # a skipped job still posts a check run under its name. GitHub treats a + # SKIPPED required check as satisfied — so if the skipped job were named + # `✅ Required PR Check`, it would green-light the ruleset (and enable + # auto-merge) while the push run's real mirror hadn't been created yet + # (it only appears once `ci` finishes), letting a PR merge mid-test or + # even override a red result. An expression name keeps the skipped job + # off the required name: GitHub does not evaluate the name of a skipped + # job, so it shows up as the raw expression text (still not the + # required name), while every path that runs evaluates to + # `✅ Required PR Check`. The condition is written out anyway so the + # name stays correct if GitHub ever starts evaluating it, and must stay + # identical to the `if:` below. + name: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == true || github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'hotfixes') && '✅ Required PR Check' || '⏭️ Required PR Check (reported by the push run)' }} needs: ci # Mirror the `ci` job's gating exactly. The four cases that run: # 1. push events (job needs CI run) @@ -312,8 +326,8 @@ jobs: # 3. release PRs from `next` → master/main (push covers SHA but commit-gate skips chore-bump) # 4. release PRs from `hotfixes` → master/main (same reason) # In-repo feature PRs targeting `next` / `hotfixes` skip on - # pull_request — push on the head branch already posted the status - # on the SHA, and mirroring here would overwrite it. + # pull_request — the push run on the head branch reports the status + # on the SHA. See the `name:` above for why the skipped job is renamed. if: | always() && ( github.event_name != 'pull_request' ||