From a095dc63cbfdc7b76b0fc67443923beb4a6411bb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 21:16:00 +0000 Subject: [PATCH] ci: supersede stale workflow runs with concurrency groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven PR-triggered workflows had no `concurrency` block, so every push to a branch left its previous run queued instead of cancelling it. A burst of PR activity therefore saturated the Actions concurrency limit with runs whose results nothing would ever read: measured at 21:12Z, 66 runs were live (63 queued behind 3 executing) and 21 of them were superseded duplicates of a run on the same workflow and branch. No PR could reach a green gate, and nothing was actually failing. Group keys avoid two traps: - `pr-checks.yml` runs on `pull_request_target`, where `github.ref` is the *base* branch. A `github.ref` key would put every open PR in one group and have them cancel each other. Keyed on the PR number, as pr-governance.yml already does. - `auto-label.yml` also runs on `issues`, where `github.ref` is likewise the default branch. Keyed on `pull_request.number || issue.number`; PRs and issues share one numbering sequence, so the two cannot collide. `cancel-in-progress` is limited to `pull_request` on the four workflows that also run on push/schedule. A push to main is the record of whether main is green, and a cancelled CodeQL or Security Scan run uploads no SARIF — that would stale the security dashboard silently rather than fail loudly. Verified with actionlint 1.7.7: clean on all seven files. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012S1Fh4RAWUcUbpy2TVtfnR --- .github/workflows/auto-label.yml | 6 ++++++ .github/workflows/ci.yml | 11 +++++++++++ .github/workflows/codeql-analysis.yml | 7 +++++++ .github/workflows/dependency-review.yml | 6 ++++++ .github/workflows/e2e-tests.yml | 5 +++++ .github/workflows/pr-checks.yml | 7 +++++++ .github/workflows/security.yml | 7 +++++++ 7 files changed, 49 insertions(+) diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml index 46ff31e6a..9b581122b 100644 --- a/.github/workflows/auto-label.yml +++ b/.github/workflows/auto-label.yml @@ -9,6 +9,12 @@ on: permissions: pull-requests: write issues: write +# See the note in ci.yml. Keyed on the PR/issue number rather than +# `github.ref`, which is the default branch for the `issues` event and would +# collapse every issue into a single group. +concurrency: + group: auto-label-${{ github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true jobs: label: runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7400d8f4..c72d1c50f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,17 @@ permissions: contents: read actions: read +# Supersede a PR's own older runs. Without this, every push to a branch left +# its previous CI run queued, and a burst of PR activity saturated the Actions +# concurrency limit with runs whose results nothing would ever read. +# `github.ref` is `refs/pull//merge` on `pull_request`, so PRs never share a +# group with each other or with main. +concurrency: + group: ci-${{ github.ref }} + # PRs only. A push to main is the record of whether main is green; cancelling + # it would leave that question unanswered for the commit that landed. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: guards: # Fail fast on the class of breakage that shipped to main un-caught: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bdad9e215..2ff3f728e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,6 +14,13 @@ permissions: security-events: write actions: read +# See the note in ci.yml. +concurrency: + group: codeql-${{ github.ref }} + # PRs only. A cancelled run on main or on the weekly schedule uploads no + # SARIF, which would silently stale the security dashboard rather than fail. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: analyze: name: "Security Scan - ${{ matrix.language }}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 1ff5a5eb5..4dfaed558 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,6 +9,12 @@ permissions: contents: read pull-requests: write +# See the note in ci.yml. This workflow is `pull_request`-only, so every run +# is superseded by the next push to the same PR. +concurrency: + group: dependency-review-${{ github.ref }} + cancel-in-progress: true + jobs: dependency-review: runs-on: ubuntu-latest diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3bc529fd7..16e9300d9 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -12,6 +12,11 @@ permissions: issues: write deployments: read +# See the note in ci.yml. +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: # By default the E2E suite runs against production. To validate a PR's own # Vercel preview instead, two things are needed: diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index b9e6a5ec1..142bff5b1 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -6,6 +6,13 @@ on: permissions: {} +# See the note in ci.yml. Keyed on the PR number, not `github.ref`: on +# `pull_request_target` the ref is the *base* branch, so every open PR would +# share one group and cancel the others. Mirrors pr-governance.yml. +concurrency: + group: pr-checks-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index dee5f5ce8..eb2053767 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -15,6 +15,13 @@ permissions: security-events: write actions: read +# See the note in ci.yml. +concurrency: + group: security-scan-${{ github.ref }} + # PRs only — this workflow also uploads security results on main and on the + # weekly schedule. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: npm-audit: runs-on: ubuntu-latest