3838 # branch-protection-required check MUST carry this trigger, or queue builds
3939 # wait forever on a check that never reports.
4040 merge_group :
41+ # ── THE HOURLY FULL RUN (#16467) ─────────────────────────────────────────
42+ # `push` above now computes the Test Core package set with `--affected`
43+ # against `github.event.before`, so a merge no longer re-tests the whole
44+ # workspace it just tested in the queue. Something still has to run the FULL
45+ # battery on `main`, on a clock, or "main is green" stops being a statement
46+ # about the workspace and becomes a statement about the last diff.
47+ #
48+ # This is that run. Minute 0 rather than an offset minute ON PURPOSE, and
49+ # against the sibling convention in test-nightly-tiers.yml: the reading this
50+ # feeds — `scripts/ci/select-shard-timings-run.mjs` — wants one complete run
51+ # per hour far more than it wants a short queue wait, and a run that starts
52+ # a few minutes late is still the hour's run. What it must NOT do is collide
53+ # with the next hour's, which the concurrency group below decides.
54+ #
55+ # ⛔ This is a NEW TRIGGER KEY, not a widening of `push:`. The ⛔ above still
56+ # binds: `push:` stays filtered to `main`.
57+ schedule :
58+ - cron : ' 0 * * * *'
59+ # The same full battery, on demand: an operator who has just landed a fix for
60+ # a red hourly run should not have to wait up to an hour to see it answered.
61+ # Treated as `full` by the selection script for the same reason `schedule` is.
62+ workflow_dispatch :
4163
4264# Superseded runs on the same PR/branch waste runners and delay feedback;
4365# cancel them. Push runs to main group by commit ref as well, so an in-flight
4466# main run is cancelled only by a newer main push.
67+ #
68+ # ⭐ `github.event_name` IS LOAD-BEARING IN THIS KEY, and it is the whole
69+ # reason the hourly run above can finish (#16467). On a `schedule` event
70+ # `github.event.pull_request.number` is empty and `github.ref` is
71+ # `refs/heads/main` — BYTE-IDENTICAL to what a push to `main` produces. Without
72+ # the event name in the key the hourly run and the next merge share one group,
73+ # `cancel-in-progress: true` applies, and the next merge kills the hourly run.
74+ # That is not a hypothetical: this card's own measurement is that 36 of the
75+ # last 60 push runs on `main` were already cancelled that way, at a merge
76+ # cadence that would censor most hours.
77+ #
78+ # ⛔ Do not "simplify" this back to two segments. `scripts/ci/scheduled-full-
79+ # run.mjs --check-concurrency` evaluates this expression against a push-shaped
80+ # and a schedule-shaped context and reds when the two groups are equal, so the
81+ # collision cannot come back silently.
82+ #
83+ # `cancel-in-progress` stays `true` for every event including `schedule`: two
84+ # hourly runs overlapping means one of them is over an hour old, and an hour-old
85+ # measurement of `main` is strictly the worse of the two. Test Core's wall clock
86+ # is well inside the cadence (~39 shard-minutes across six shards), so this is
87+ # the exceptional path, not the normal one.
4588concurrency :
46- group : ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
89+ group : ci-${{ github.workflow }}-${{ github.event_name }}-${{ github. event.pull_request.number || github.ref }}
4790 cancel-in-progress : true
4891
4992jobs :
@@ -54,11 +97,13 @@ jobs:
5497 contents : read
5598 pull-requests : read
5699 outputs :
57- # On merge_group, everything counts as changed: dorny/paths-filter has no
58- # merge_group support, and the queue build is the last validation before
59- # main — the one place a skipped job can never be the right answer. A
60- # skipped step's output is the empty string (falsy), so `|| 'true'`
61- # supplies the merge-group value without touching PR/push behavior.
100+ # On merge_group — and on the `schedule` / `workflow_dispatch` full runs
101+ # (#16467) — everything counts as changed: dorny/paths-filter resolves a
102+ # diff only on `pull_request` and `push`, and each of these three events
103+ # is a place a skipped job can never be the right answer (the queue build
104+ # is the last validation before main; the hourly run IS the full battery).
105+ # A skipped step's output is the empty string (falsy), so `|| 'true'`
106+ # supplies their value without touching PR/push behavior.
62107 docs : ${{ steps.changes.outputs.docs || 'true' }}
63108 core : ${{ steps.changes.outputs.core || 'true' }}
64109 console : ${{ steps.changes.outputs.console || 'true' }}
@@ -100,9 +145,24 @@ jobs:
100145 - name : Checkout repository
101146 uses : actions/checkout@v7
102147
148+ # ⭐ SPELLED AS AN ALLOW-LIST, not as `!= 'merge_group'` (#16467). This
149+ # action reads a diff, and the only two events that hand it one it can
150+ # resolve unaided are `pull_request` (base vs head) and `push`
151+ # (`event.before` vs `after`). `merge_group` was the first event with
152+ # neither, and the exclusion was written as its name; `schedule` and
153+ # `workflow_dispatch` are the second and third, and under the old
154+ # spelling they would have RUN the action with no resolvable base.
155+ #
156+ # That failure direction is the dangerous one: every filter output would
157+ # come back `false`, `|| 'true'` supplies nothing (the output exists and
158+ # says `false`), and every downstream `!= 'false'` guard SKIPS — so the
159+ # hourly full run would have been an entirely green, entirely empty run
160+ # of nothing at all. An allow-list makes each new event fail toward
161+ # THE FILTER CONTRACT's half 1 instead: the step is skipped, its outputs
162+ # are the empty string, `|| 'true'` fills them in, and everything runs.
103163 - uses : dorny/paths-filter@v4
104164 id : changes
105- if : github.event_name != 'merge_group '
165+ if : github.event_name == 'pull_request' || github.event_name == 'push '
106166 with :
107167 filters : |
108168 docs:
@@ -473,13 +533,21 @@ jobs:
473533 # on this shard" exit below, every shard still attests,
474534 # and Test Core is an honest green. It is NOT the #10057
475535 # case, which is pull_request-only (the script says why).
476- # push unchanged: the FULL list.
536+ # push affected set against `github.event.before`, the commit
537+ # `main` was on before this merge landed (#16467). A zero
538+ # sha -- a first push, or a force-push that rewrote
539+ # history -- falls back to the FULL list, loudly.
540+ # schedule the FULL list: the hourly run IS the full battery, and
541+ # workflow_ the on-demand rerun of it. Neither carries a diff base,
542+ # dispatch and neither should: they are the run that says whether
543+ # the whole workspace is green on `main`.
477544 - name : Compute this shard's package set
478545 env :
479546 OS_SHARD_EVENT_NAME : ${{ github.event_name }}
480547 OS_SHARD_PR_BASE_REF : ${{ github.event.pull_request.base.ref }}
481548 OS_SHARD_PR_PINNED_BASE_SHA : ${{ github.event.pull_request.base.sha }}
482549 OS_SHARD_MERGE_GROUP_BASE_SHA : ${{ github.event.merge_group.base_sha }}
550+ OS_SHARD_PUSH_BEFORE_SHA : ${{ github.event.before }}
483551 run : |
484552 bash scripts/ci/select-shard-packages.sh
485553 node scripts/partition-test-shards.mjs "$RUNNER_TEMP/turbo-ls.json" \
0 commit comments