From a0e7282c92f6533e218634e691674095888d05ef Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 00:30:41 -0700 Subject: [PATCH 1/2] chore(ci): cancel superseded pull request runs via concurrency groups Every push to an open pull request started a fresh, additive set of CI runs while the previous runs for the same pull request kept executing to completion. Those superseded runs test a commit that has already been replaced, so their result is discarded -- the minutes are pure waste, and the worst offender runs on macos-latest. Add a workflow-scoped concurrency group keyed on the pull request number to the three pull_request-triggered workflows, so a newer run cancels the in-progress one. The group key falls back to the unique run id on non-pull-request events, which keeps every push-to-main run alone in its group: a merge can therefore neither cancel nor queue behind an in-progress main run. release-please.yml, auto-assignment.yml and csat.yml are deliberately untouched -- none of them can be superseded, and cancelling a release run would be harmful. --- .github/workflows/cross-language-integration.yml | 5 +++++ .github/workflows/license-check.yml | 5 +++++ .github/workflows/validation.yaml | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/cross-language-integration.yml b/.github/workflows/cross-language-integration.yml index 9ba2a2d7b..3b092cae2 100644 --- a/.github/workflows/cross-language-integration.yml +++ b/.github/workflows/cross-language-integration.yml @@ -6,6 +6,11 @@ on: pull_request: branches: [main] +# Cancel superseded PR runs; the run_id fallback keeps push-to-main runs ungrouped. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + jobs: run-tests: runs-on: macos-latest diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 743de0138..534af9236 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -6,6 +6,11 @@ on: pull_request: branches: [main] +# Cancel superseded PR runs; the run_id fallback keeps push-to-main runs ungrouped. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + jobs: check-license: runs-on: ubuntu-latest diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 7bb00098e..7027cc0c5 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -6,6 +6,11 @@ on: pull_request: branches: [main] +# Cancel superseded PR runs; the run_id fallback keeps push-to-main runs ungrouped. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + env: NODE_OPTIONS: "--max-old-space-size=8192" From c7a47e475e2e4016cf539c04383f816af3ff03ac Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 00:30:43 -0700 Subject: [PATCH 2/2] test: pin the workflow concurrency policy against regression Discovers supersedable workflows from their pull_request activity types rather than hard-coding a file list, so a workflow added later is covered automatically, and encodes the rule that release-please must never cancel an in-progress run. --- .../workflows/workflow_concurrency_test.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/integration/workflows/workflow_concurrency_test.ts diff --git a/tests/integration/workflows/workflow_concurrency_test.ts b/tests/integration/workflows/workflow_concurrency_test.ts new file mode 100644 index 000000000..960d85d37 --- /dev/null +++ b/tests/integration/workflows/workflow_concurrency_test.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import yaml from 'js-yaml'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import {describe, expect, it} from 'vitest'; + +const WORKFLOWS_DIR = path.join(process.cwd(), '.github/workflows'); + +/** Activity types GitHub uses when a `pull_request` trigger omits `types`. */ +const DEFAULT_PULL_REQUEST_TYPES = ['opened', 'synchronize', 'reopened']; + +/** The subset of a workflow file this test inspects. */ +interface Workflow { + on?: {pull_request?: {types?: string[]} | null}; + concurrency?: {group?: string; 'cancel-in-progress'?: boolean}; +} + +function isWorkflow(value: unknown): value is Workflow { + return typeof value === 'object' && value !== null; +} + +function loadWorkflow(file: string): Workflow { + const source = fs.readFileSync(path.join(WORKFLOWS_DIR, file), 'utf8'); + const parsed: unknown = yaml.load(source); + return isWorkflow(parsed) ? parsed : {}; +} + +/** + * A workflow can be superseded only if a later push to the same pull request + * re-triggers it, i.e. if its `pull_request` activity types include + * `synchronize`. + */ +function isSupersedable(workflow: Workflow): boolean { + const pullRequest = workflow.on?.pull_request; + if (pullRequest === undefined) { + return false; + } + const types = pullRequest?.types ?? DEFAULT_PULL_REQUEST_TYPES; + return types.includes('synchronize'); +} + +const workflows = fs + .readdirSync(WORKFLOWS_DIR) + .filter((file) => file.endsWith('.yml') || file.endsWith('.yaml')) + .map((file): [string, Workflow] => [file, loadWorkflow(file)]); + +const supersedable = workflows.filter(([, workflow]) => + isSupersedable(workflow), +); + +describe('Workflow concurrency', () => { + it('discovers at least one supersedable workflow', () => { + for (const [file, workflow] of workflows) { + // js-yaml keeps `on` a string key (YAML 1.2); a missing trigger block + // would make every assertion below vacuous. + expect( + workflow.on, + `${file} declares no 'on' trigger block`, + ).toBeDefined(); + } + + expect(supersedable.length).toBeGreaterThan(0); + }); + + it.each(supersedable)( + '%s cancels superseded pull request runs', + (file, workflow) => { + const concurrency = workflow.concurrency; + expect( + concurrency, + `${file} declares no concurrency group`, + ).toBeDefined(); + + // The run-id fallback is what keeps runs on main out of a shared group. + expect(concurrency?.group).toContain('github.event.pull_request.number'); + expect(concurrency?.group).toContain('github.run_id'); + expect(concurrency?.['cancel-in-progress']).toBe(true); + }, + ); + + it('release-please.yml never cancels an in-progress release run', () => { + const {concurrency} = loadWorkflow('release-please.yml'); + + expect(concurrency?.['cancel-in-progress'] ?? false).toBe(false); + }); +});