From be7ccbf1132708b065834a3e651543ce87755509 Mon Sep 17 00:00:00 2001 From: Austen Stone Date: Sun, 11 Oct 2026 23:49:59 -0700 Subject: [PATCH] ci: skip re-running checks for PR title and body edits on a passing commit A title or body edit fires ci, regression and windows-render again on the same head commit. When the previous run of that workflow on the commit passed, Detect changes now reports no changes, so the check jobs skip and their required checks still pass. Base changes, first runs and runs whose predecessor failed, was cancelled or is unfinished still run everything. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../actions/edit-already-passed/action.yml | 36 +++++++++++++++++++ .github/workflows/ci.yml | 8 +++++ .github/workflows/regression.yml | 10 ++++-- .github/workflows/windows-render.yml | 7 ++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 .github/actions/edit-already-passed/action.yml diff --git a/.github/actions/edit-already-passed/action.yml b/.github/actions/edit-already-passed/action.yml new file mode 100644 index 00000000000..10ac7cc3144 --- /dev/null +++ b/.github/actions/edit-already-passed/action.yml @@ -0,0 +1,36 @@ +name: Edit already passed +description: | + Reports whether this run comes from a pull request title or body edit on a + head commit whose previous run of the same workflow passed. Such an edit + cannot change the result, so callers skip their checks and the skipped + required checks still pass. A base change, a first run, or a previous run + that failed, was cancelled or is unfinished returns false and runs them again. + The calling job needs `actions: read`. + +outputs: + passed: + description: "`true` when the previous run on this head commit passed." + value: ${{ steps.previous.outputs.passed }} + +runs: + using: composite + steps: + - id: previous + if: github.event.action == 'edited' && github.event.changes.base == null + shell: bash + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + workflow="$(basename "${GITHUB_WORKFLOW_REF%%@*}")" + conclusion="$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/$workflow/runs?event=pull_request&head_sha=$HEAD_SHA&per_page=100" --jq ' + [.workflow_runs[] + | select(.id < (env.GITHUB_RUN_ID | tonumber) + and .head_branch == env.HEAD_REF + and .head_repository.full_name == env.HEAD_REPO)] + | max_by(.id) | .conclusion // "none"')" || conclusion="lookup failed" + echo "Previous $workflow run on $HEAD_SHA: $conclusion" + if [ "$conclusion" = success ]; then passed=true; else passed=false; fi + echo "passed=$passed" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86b8f6fc959..616bc1512cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,10 @@ jobs: name: Detect changes runs-on: ubuntu-latest timeout-minutes: 2 + permissions: + actions: read + contents: read + pull-requests: read outputs: code: ${{ (github.event_name == 'workflow_dispatch' && inputs.catalog_publish) && 'true' || steps.filter.outputs.code }} docs_catalog: ${{ steps.filter.outputs.docs_catalog }} @@ -83,6 +87,9 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + - name: Check whether this commit already passed before a title or body edit + id: edit + uses: ./.github/actions/edit-already-passed - name: Reject accidental file deletions if: github.event_name == 'pull_request' run: node scripts/check-no-main-deletions.mjs --base origin/main @@ -102,6 +109,7 @@ jobs: run: sh scripts/check-large-files.sh --range "$BASE_SHA" HEAD - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 id: filter + if: steps.edit.outputs.passed != 'true' with: token: "" base: ${{ steps.base.outputs.sha }} diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 8374cee9e5d..7401a42c58f 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -40,8 +40,11 @@ jobs: name: Detect changes runs-on: ubuntu-latest timeout-minutes: 2 + permissions: + actions: read + contents: read outputs: - code: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.catalog_publish)) && 'true' || steps.filter.outputs.code }} + code: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.catalog_publish)) && 'true' || steps.edit.outputs.passed == 'true' && 'false' || steps.filter.outputs.code }} matrix: ${{ steps.shards.outputs.matrix }} steps: # Force git-based change detection instead of the pull_request REST API. @@ -50,6 +53,9 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + - name: Check whether this commit already passed before a title or body edit + id: edit + uses: ./.github/actions/edit-already-passed # HEAD is the PR merge ref; its first parent is the current base tip, unlike the payload's # base.sha, which goes stale and would count the base's newer commits as the PR's changes. - name: Find the pull request's current base @@ -61,7 +67,7 @@ jobs: echo "sha=$sha" >> "$GITHUB_OUTPUT" - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 id: filter - if: github.event_name != 'schedule' + if: github.event_name != 'schedule' && steps.edit.outputs.passed != 'true' with: token: "" base: ${{ steps.base.outputs.sha }} diff --git a/.github/workflows/windows-render.yml b/.github/workflows/windows-render.yml index 7c717fab650..1bdbcec71de 100644 --- a/.github/workflows/windows-render.yml +++ b/.github/workflows/windows-render.yml @@ -44,6 +44,9 @@ jobs: name: Detect changes runs-on: ubuntu-latest timeout-minutes: 2 + permissions: + actions: read + contents: read outputs: code: ${{ steps.filter.outputs.code }} steps: @@ -53,6 +56,9 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + - name: Check whether this commit already passed before a title or body edit + id: edit + uses: ./.github/actions/edit-already-passed # HEAD is the PR merge ref; its first parent is the current base tip, unlike the payload's # base.sha, which goes stale and would count the base's newer commits as the PR's changes. - name: Find the pull request's current base @@ -64,6 +70,7 @@ jobs: echo "sha=$sha" >> "$GITHUB_OUTPUT" - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 id: filter + if: steps.edit.outputs.passed != 'true' with: token: "" base: ${{ steps.base.outputs.sha }}