From 326f7d5d2f1f1a60f73f8ca350bddd21383b42da Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 05:55:04 +0000 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E5=88=86=E7=89=87=20affected=20?= =?UTF-8?q?=E9=9B=86=E5=90=88=E4=BB=8E=20merge-base=20=E8=B5=B7=E7=AE=97,?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=90=83=E5=86=BB=E7=BB=93=E7=9A=84=20base.s?= =?UTF-8?q?ha=20(#6195)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 101 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91cd98fbee..e1dfe2540b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,13 +215,110 @@ jobs: # naming the cause, not a silently empty shard. An EMPTY shard file must # short-circuit the test step below: `turbo run test` with zero --filter # args runs the entire workspace. + # + # WHERE THE AFFECTED DIFF STARTS (#6195, the #6129 family's third + # consumer). The one thing this base must never be is + # `github.event.pull_request.base.sha`. + # + # The payload's `base.sha` is frozen when the PR is OPENED and does not + # move on `synchronize`. HEAD, meanwhile, is the merge ref + # (`refs/pull/N/merge`) that the checkout above resolves by default on a + # `pull_request` event — no `ref:` is given, so this job stands on a merge + # commit containing everything main has today. Everything main gained + # while the PR sat open therefore lands between the two, and + # `turbo ls --affected` reads it as this PR's own changes: packages only + # SOMEBODY ELSE's merged PR touched get tested on this shard. + # + # The direction is conservative — the frozen base is an ancestor of HEAD, + # so its file set is a strict SUPERSET of this PR's own. Nothing that + # should run is skipped; what degrades is the optimisation itself, and it + # degrades with how long the PR has been open. At ~18 merges a day an + # affected-only shard drifts back toward a full run. + # + # Measured on turbo 2.10.7 against a real merge-ref fixture (base branch + # moved 1 commit touching pkg-b; this PR touched pkg-a only): + # TURBO_SCM_BASE= -> pkg-a, pkg-b + # TURBO_SCM_BASE=merge-base(origin/main,HEAD) -> pkg-a + # + # Four spellings that look like the fix and are not: + # - `base.sha...HEAD` (three dots). Three-dot means + # `merge-base(base.sha, HEAD)..HEAD`, and the frozen sha is ALREADY an + # ancestor of HEAD, so it IS its own merge base and the set does not + # move. Measured: still both packages. (pr-automation.yml records the + # same result for its own diff — same fact, one family.) + # - `HEAD^1`. Correct on a merge ref and silently wrong the day someone + # gives this checkout a `ref:`, where parent^1 becomes the PR's + # previous commit. `merge-base` is right under BOTH checkouts. + # - Dropping the variable and letting turbo default to `main`. Measured + # in a CI-shaped clone: `fetch-depth: 0` populates + # `refs/remotes/origin/*`, NOT local heads, so there is no local `main` + # — and turbo does not error, it silently returns the ENTIRE + # workspace, untouched packages included. Safe, and the whole + # optimisation gone. + # - `TURBO_SCM_BASE=origin/$BASE_REF`, letting turbo resolve the ref. + # Measured correct today, but it rests the shard's package set on + # `turbo ls`'s internal choice of dot-ness — undocumented, and + # `turbo ls` is experimental (see above). Resolving to a commit here + # leaves turbo no choice to make. - name: Compute this shard's package set env: - TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + PINNED_BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | + SCM_BASE='' if [ "${{ github.event_name }}" = "pull_request" ]; then - pnpm exec turbo ls --affected --output=json > "$RUNNER_TEMP/turbo-ls.json" + if [ -z "$BASE_REF" ]; then + echo "::warning::This pull_request event carries no base branch, so the affected-set diff base cannot be computed." + else + # `fetch-depth: 0` above already makes this resolve — the fetch is + # the guard for the day that changes, not the normal path. + # + # `git cat-file -e` rather than the more idiomatic strict + # `git rev-parse` spelling, and the reason is not style. + # check-shard-attestation.mjs classifies a job as an aggregate GATE + # when the job's joined `run:` text contains BOTH the string + # "check-shard-attestation.mjs" and, as a bare substring, that + # script's own dash-dash-verify flag. This job already carries the + # first (its --emit step at the bottom), so spelling that flag + # anywhere in this step — including in a comment, since comments + # are part of `run:` — silently reclassifies the shard job as a + # gate. The drift guard then fails with two complaints that name + # nothing to do with the real edit ("its job-level if: is not + # always()", "never downloads the shard attestations it claims to + # count"). Measured both ways on this very change; do not "tidy" + # this back. + if ! git cat-file -e "refs/remotes/origin/$BASE_REF^{commit}" 2>/dev/null; then + git fetch --no-tags --quiet origin "+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF" \ + || echo "::warning::Could not fetch origin/$BASE_REF; the merge-base resolution below will decide." + fi + # `if !` rather than a bare assignment on purpose: these steps run + # under `bash -e`, where a failing command substitution kills the + # step with no message at all. + if ! SCM_BASE=$(git merge-base "refs/remotes/origin/$BASE_REF" HEAD); then + SCM_BASE='' + fi + fi + fi + if [ -n "$SCM_BASE" ]; then + # The drift is printed, not just corrected: nothing in this log ever + # said which commit the affected diff started from, which is why the + # decay was invisible. + DRIFT=$(git rev-list --count "$PINNED_BASE_SHA..$SCM_BASE" 2>/dev/null || echo '?') + echo "Affected-set diff base: $SCM_BASE (merge-base of origin/$BASE_REF and HEAD)" + echo "Frozen payload base.sha: $PINNED_BASE_SHA -- $BASE_REF has moved $DRIFT commit(s) since it was frozen, and that drift is exactly what this step used to charge to this PR." + TURBO_SCM_BASE="$SCM_BASE" pnpm exec turbo ls --affected --output=json > "$RUNNER_TEMP/turbo-ls.json" else + # Falling back to the FULL package list, never to the frozen + # base.sha. This is not the #4690 silent-skip anti-pattern: that is + # about a gate PASSING on input it could not read, and the full list + # is a strict superset of the affected one — this shard still runs + # everything it would have run and more. Cost is minutes; the + # alternative is a red Test Core on a PR with nothing wrong with it. + # Push and merge-queue builds take this branch by design (the queue + # result IS the next main, so it gets main's validation). + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "::warning::Could not resolve merge-base(origin/$BASE_REF, HEAD); falling back to the full package list for this shard rather than diffing from the frozen base.sha (#6195)." + fi pnpm exec turbo ls --output=json > "$RUNNER_TEMP/turbo-ls.json" fi node scripts/partition-test-shards.mjs "$RUNNER_TEMP/turbo-ls.json" \