Skip to content

Gate pull requests on one aggregate check instead of eight job names #336

Description

@taras

Why

Ruleset 13643935 on main carries deletion, non_fast_forward, and
pull_request, and no required status checks. A pull request whose CI is
red merges exactly as easily as one whose CI is green.

The obvious fix is wrong. GitHub's required-status-checks rule matches
check-run names, and every job is its own check run — there is no built-in
check meaning "the CI workflow passed". Naming ci.yml's eight jobs
individually puts a hand-maintained copy of that job list into repository
settings, where a job that is added and not listed is silently ungated and
nothing reports it.

Recorded as a decision in DEC-013 (specs/decisions.md) by #333, which
implements the complementary half — reporting a red main. Gating pull
requests does not reach a push to main, and reporting a red main does not
gate pull requests; neither waits on the other.

What to build

One aggregate job in ci.yml that the ruleset can require by name:

green:
needs: [lint, test-deno, jsr, smoke, composability, site, test-node, test-bun]if: always()runs-on: ubuntu-lateststeps:
- name: Every CI job succeededenv:
RESULTS: ${{ toJSON(needs) }}run: | # any dep whose result is neither success nor skipped fails the gate

Two things are load-bearing rather than stylistic:

  • if: always(). Without it a failed dependency skips this job, and
    GitHub counts a skipped required check as satisfied — so the gate would
    pass precisely when the tree is broken.
  • skipped counts as success, cancelled does not. A job whose own if
    excludes it from this event is saying it does not apply here, not that it went
    unproven. This is what lets a job become conditional — composability moving
    to main-only, for instance — without the gate changing.

A prototype of exactly this passed on
#333's first head,
reading all eight results and printing them before passing. It was removed from
that branch to keep #331's split; the shape is known to work.

Also needed

  • A regression that the needs list still covers every job. Parse ci.yml,
    compare its job ids against green's needs, and fail on a job left off.
    Without it the hand-maintained list moves from settings into the repository
    and stays just as silent.
  • A regression that if: always() is present, for the reason above.

Before requiring it

bypass_actors on ruleset 13643935 is [] and current_user_can_bypass is
never. Requiring green with that unchanged means a red main freezes every
unrelated pull request with no admin-merge escape — the only way out would be
editing the ruleset. Add a bypass actor first.

Note also what this does not fix: a pull request that passed its checks before
main moved keeps that green result, because GitHub recomputes the merge ref
without re-running the checks. That is how main went red in #332. Only a merge
queue closes it, and DEC-013 records that as a deliberate "not now".

Done when

  • ci.yml has the aggregate job, with both regressions.
  • The ruleset requires that one check, after it has passed on a real pull
    request and after a bypass actor exists.
  • AGENTS.md §Verification says which check is required and how to cover a new
    job.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions