From 8f011c93757b5a554bcaa045afe884ed95baa97e Mon Sep 17 00:00:00 2001 From: Holden Date: Tue, 25 Aug 2026 11:03:46 +0000 Subject: [PATCH] ci: classify a PR by its own diff, not by everything main gained since (#2104) The `changes` job used a two-dot diff against `github.event.pull_request.base.sha`. That SHA is the base branch tip at the moment the event fired, not the PR's merge-base, so the diff also reported every file main gained since the branch diverged -- with the sign inverted, since the branch "lacks" them. A pure-markdown PR is then classified as code and takes the full matrix. Measured on #2103, which changes one .md file: two-dot reported 9 files, three-dot reported 1, and `gh pr view --json files` (ground truth) reports 1. The 8 extra are exactly #2091's file list -- #2091 being the commit that `base.sha` pointed at. Intermittent, not constant: #2070 (three .md files) classified docs_only=true and skipped all nine lanes, because its branch happened to be level with main when the event fired. The trigger is exactly "main moved between the merge-base and the event", which is why the fast path looks like it works whenever you check it deliberately. Fail-closed direction -- more CI, never less -- so this is cost and latency, not correctness. It matters because runner capacity is the binding constraint. `push` stays two-dot: before..after is the push itself. Failure behaviour is unchanged: if the merge-base is unavailable the diff errors, `|| true` leaves `files` empty, and the existing `elif [ -n "$files" ]` leaves docs_only=false. `fetch-depth: 0` is already set on this job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f3a95f35c..b4e24f0db5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,10 +156,20 @@ jobs: case "$EVENT_NAME" in pull_request) if [ -n "${BASE_SHA:-}" ] && [ -n "${HEAD_SHA:-}" ]; then - files="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" 2>/dev/null || true)" + # Three-dot: the PR's own changes, i.e. what GitHub shows under + # "Files changed". Two-dot against `base.sha` -- the base branch + # tip when the event fired, not the merge-base -- also reports + # every file main gained since the branch diverged, so a + # pure-docs PR is classified as code whenever main moves while + # it is open (#2104: #2103's one .md edit reported 9 files, the + # 8 extra being #2091's, which is `base.sha` itself). + # Fails closed: if the merge-base is missing this + # errors, `files` stays empty, and docs_only stays false. + files="$(git diff --name-only "$BASE_SHA...$HEAD_SHA" 2>/dev/null || true)" fi ;; push) + # Two-dot is correct here: before..after is the push itself. if [ -n "${BEFORE_SHA:-}" ] \ && [ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ] \ && git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then