From cf763073d3048ad1f3daed901d28b92b0bbef649 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 20 Oct 2026 23:42:10 +0530 Subject: [PATCH 1/4] Queue pull request CI runs per author and workflow One author opening or pushing many pull requests could fill the runner queue and delay everyone else. Each PR-triggered workflow now uses a concurrency group keyed on the workflow and the actor, so an author has one run in progress per workflow while the rest wait in order (queue: max) rather than being cancelled. Pushes to main, scheduled runs and manual dispatches fall back to the run ID and are never held back. Signed-off-by: dhruv8sh --- .github/workflows/codeql.yml | 9 +++++++++ .github/workflows/format.yml | 9 +++++++++ .github/workflows/integration-tests.yml | 9 +++++++++ .github/workflows/pbs-example.yml | 9 +++++++++ .github/workflows/test.yml | 9 +++++++++ 5 files changed, 45 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4b963e0153..0a03f49e82 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,6 +19,15 @@ on: schedule: - cron: "42 6 * * 0" +# Each author runs one pull request at a time per workflow, so one person +# pushing many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled. Other events get their +# own group from the run ID and are never held back. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + cancel-in-progress: false + queue: max + jobs: analyze: name: Analyze (${{ matrix.language }}) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index f458572eb7..be6543b5d0 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,6 +8,15 @@ on: permissions: contents: read +# Each author runs one pull request at a time per workflow, so one person +# pushing many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled. Other events get their +# own group from the run ID and are never held back. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + cancel-in-progress: false + queue: max + jobs: check-claude-md-symlink: name: CLAUDE.md symlink guard diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 157a3582d3..d5cf97b726 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,6 +10,15 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: +# Each author runs one pull request at a time per workflow, so one person +# pushing many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled. Other events get their +# own group from the run ID and are never held back. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + cancel-in-progress: false + queue: max + env: ORIGIN_PORT: 8888 ARTIFACTS_DIR: /tmp/integration-test-artifacts diff --git a/.github/workflows/pbs-example.yml b/.github/workflows/pbs-example.yml index dced0c7b63..ca3245d0c0 100644 --- a/.github/workflows/pbs-example.yml +++ b/.github/workflows/pbs-example.yml @@ -17,6 +17,15 @@ on: permissions: contents: read +# Each author runs one pull request at a time per workflow, so one person +# pushing many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled. Other events get their +# own group from the run ID and are never held back. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + cancel-in-progress: false + queue: max + jobs: local-checks: name: PBS Terraform and Compose checks diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b27173dd66..777578bb36 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,15 @@ on: branches: [main] pull_request: +# Each author runs one pull request at a time per workflow, so one person +# pushing many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled. Other events get their +# own group from the run ID and are never held back. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + cancel-in-progress: false + queue: max + jobs: test-rust: name: cargo test From 68e005b052c96754a5e5fc8f8a94000c56999e58 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Wed, 21 Oct 2026 00:47:36 +0530 Subject: [PATCH 2/4] Key the CI queue on the PR author and add job timeouts Group pull request runs by the PR author instead of the triggering actor, so a maintainer pushing to or updating contributors' branches does not pile those runs into the maintainer's queue. Bot-authored PRs fall back to the run ID so a Dependabot batch is not serialized. Give every job in the queued workflows a timeout of roughly three times its longest recent run, so a hung job cannot hold an author's queue for GitHub's six-hour default. Signed-off-by: dhruv8sh --- .github/workflows/codeql.yml | 12 +++++++----- .github/workflows/format.yml | 16 +++++++++++----- .github/workflows/integration-tests.yml | 11 ++++++----- .github/workflows/pbs-example.yml | 11 ++++++----- .github/workflows/test.yml | 17 ++++++++++++----- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0a03f49e82..09f03e0e0a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,12 +19,13 @@ on: schedule: - cron: "42 6 * * 0" -# Each author runs one pull request at a time per workflow, so one person -# pushing many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled. Other events get their -# own group from the run ID and are never held back. +# Each pull request author runs one PR at a time per workflow, so one person +# opening many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled, and every job has a +# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and +# other events get their own group from the run ID and are never held back. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} cancel-in-progress: false queue: max @@ -37,6 +38,7 @@ jobs: # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: 45 permissions: # required for all workflows security-events: write diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index be6543b5d0..7de102c2de 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,12 +8,13 @@ on: permissions: contents: read -# Each author runs one pull request at a time per workflow, so one person -# pushing many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled. Other events get their -# own group from the run ID and are never held back. +# Each pull request author runs one PR at a time per workflow, so one person +# opening many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled, and every job has a +# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and +# other events get their own group from the run ID and are never held back. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} cancel-in-progress: false queue: max @@ -21,6 +22,7 @@ jobs: check-claude-md-symlink: name: CLAUDE.md symlink guard runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -51,6 +53,7 @@ jobs: format-rust: name: cargo fmt runs-on: ubuntu-latest + timeout-minutes: 25 steps: - uses: actions/checkout@v4 @@ -112,6 +115,7 @@ jobs: format-typescript: runs-on: ubuntu-latest + timeout-minutes: 10 defaults: run: working-directory: crates/trusted-server-js/lib @@ -143,6 +147,7 @@ jobs: format-docs: runs-on: ubuntu-latest + timeout-minutes: 10 defaults: run: working-directory: docs @@ -184,6 +189,7 @@ jobs: docs-proposal-scripts: name: docs-proposal scripts runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d5cf97b726..2346f5a525 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,12 +10,13 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: -# Each author runs one pull request at a time per workflow, so one person -# pushing many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled. Other events get their -# own group from the run ID and are never held back. +# Each pull request author runs one PR at a time per workflow, so one person +# opening many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled, and every job has a +# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and +# other events get their own group from the run ID and are never held back. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} cancel-in-progress: false queue: max diff --git a/.github/workflows/pbs-example.yml b/.github/workflows/pbs-example.yml index ca3245d0c0..2c1fb07303 100644 --- a/.github/workflows/pbs-example.yml +++ b/.github/workflows/pbs-example.yml @@ -17,12 +17,13 @@ on: permissions: contents: read -# Each author runs one pull request at a time per workflow, so one person -# pushing many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled. Other events get their -# own group from the run ID and are never held back. +# Each pull request author runs one PR at a time per workflow, so one person +# opening many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled, and every job has a +# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and +# other events get their own group from the run ID and are never held back. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} cancel-in-progress: false queue: max diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 777578bb36..0514afee2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,13 @@ on: branches: [main] pull_request: -# Each author runs one pull request at a time per workflow, so one person -# pushing many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled. Other events get their -# own group from the run ID and are never held back. +# Each pull request author runs one PR at a time per workflow, so one person +# opening many PRs cannot fill the runner queue ahead of everyone else. Their +# other runs wait in order instead of being cancelled, and every job has a +# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and +# other events get their own group from the run ID and are never held back. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.actor || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} cancel-in-progress: false queue: max @@ -77,6 +78,7 @@ jobs: test-axum: name: cargo test (axum native) runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 @@ -125,6 +127,7 @@ jobs: test-cloudflare: name: cargo check (cloudflare native + wasm32-unknown-unknown) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -146,6 +149,7 @@ jobs: test-spin: name: cargo check/build/test (spin native + wasm32-wasip1) runs-on: ubuntu-latest + timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -179,6 +183,7 @@ jobs: test-parity: name: cargo test (cross-adapter parity) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -204,6 +209,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 45 steps: - uses: actions/checkout@v4 @@ -268,6 +274,7 @@ jobs: test-typescript: name: vitest runs-on: ubuntu-latest + timeout-minutes: 10 defaults: run: working-directory: crates/trusted-server-js/lib From 7074ec1c56becff04458cb1a087a84b8cb562111 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 27 Oct 2026 21:46:12 +0530 Subject: [PATCH 3/4] Cancel superseded PR runs and queue each job per author A workflow-level group per pull request now cancels a run when a newer push arrives, so stale runs no longer hold an author's queue or count toward GitHub's 100-pending cap. The per-author queue moves to each job's own concurrency block with queue: max, keyed on the matrix for matrix jobs so their legs do not queue behind each other. Bot-authored PRs and non-PR events still key on the run ID. The shared comment now states the FIFO ordering and the 100-pending limit. Signed-off-by: dhruv8sh --- .github/workflows/codeql.yml | 20 +++++++----- .github/workflows/format.yml | 32 ++++++++++++++----- .github/workflows/integration-tests.yml | 29 ++++++++++++----- .github/workflows/pbs-example.yml | 20 +++++++----- .github/workflows/test.yml | 41 ++++++++++++++++++++----- 5 files changed, 102 insertions(+), 40 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 09f03e0e0a..1d3f825ffe 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,15 +19,16 @@ on: schedule: - cron: "42 6 * * 0" -# Each pull request author runs one PR at a time per workflow, so one person -# opening many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled, and every job has a -# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and -# other events get their own group from the run ID and are never held back. +# A newer push to a pull request cancels that PR's superseded run. Each job +# then joins a per-author queue for that job (its own `concurrency` block), so +# one person opening many PRs runs one copy of each job at a time and cannot +# fill the runner pool ahead of everyone else. Queued jobs start in the order they began waiting; GitHub +# keeps up to 100 pending per queue and cancels arrivals past that. Every job +# has a timeout so a hung job cannot hold its queue for hours. Bot-authored +# PRs and non-PR events key on the run ID and never wait on a queue. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} - cancel-in-progress: false - queue: max + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true jobs: analyze: @@ -39,6 +40,9 @@ jobs: # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} timeout-minutes: 45 + concurrency: + group: ${{ github.workflow }}-analyze-${{ matrix.language }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max permissions: # required for all workflows security-events: write diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7de102c2de..1d03f47364 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,21 +8,25 @@ on: permissions: contents: read -# Each pull request author runs one PR at a time per workflow, so one person -# opening many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled, and every job has a -# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and -# other events get their own group from the run ID and are never held back. +# A newer push to a pull request cancels that PR's superseded run. Each job +# then joins a per-author queue for that job (its own `concurrency` block), so +# one person opening many PRs runs one copy of each job at a time and cannot +# fill the runner pool ahead of everyone else. Queued jobs start in the order they began waiting; GitHub +# keeps up to 100 pending per queue and cancels arrivals past that. Every job +# has a timeout so a hung job cannot hold its queue for hours. Bot-authored +# PRs and non-PR events key on the run ID and never wait on a queue. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} - cancel-in-progress: false - queue: max + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true jobs: check-claude-md-symlink: name: CLAUDE.md symlink guard runs-on: ubuntu-latest timeout-minutes: 5 + concurrency: + group: ${{ github.workflow }}-check-claude-md-symlink-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -54,6 +58,9 @@ jobs: name: cargo fmt runs-on: ubuntu-latest timeout-minutes: 25 + concurrency: + group: ${{ github.workflow }}-format-rust-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -116,6 +123,9 @@ jobs: format-typescript: runs-on: ubuntu-latest timeout-minutes: 10 + concurrency: + group: ${{ github.workflow }}-format-typescript-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max defaults: run: working-directory: crates/trusted-server-js/lib @@ -148,6 +158,9 @@ jobs: format-docs: runs-on: ubuntu-latest timeout-minutes: 10 + concurrency: + group: ${{ github.workflow }}-format-docs-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max defaults: run: working-directory: docs @@ -190,6 +203,9 @@ jobs: name: docs-proposal scripts runs-on: ubuntu-latest timeout-minutes: 10 + concurrency: + group: ${{ github.workflow }}-docs-proposal-scripts-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3867d79224..a276d23ecc 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,15 +10,16 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: -# Each pull request author runs one PR at a time per workflow, so one person -# opening many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled, and every job has a -# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and -# other events get their own group from the run ID and are never held back. +# A newer push to a pull request cancels that PR's superseded run. Each job +# then joins a per-author queue for that job (its own `concurrency` block), so +# one person opening many PRs runs one copy of each job at a time and cannot +# fill the runner pool ahead of everyone else. Queued jobs start in the order they began waiting; GitHub +# keeps up to 100 pending per queue and cancels arrivals past that. Every job +# has a timeout so a hung job cannot hold its queue for hours. Bot-authored +# PRs and non-PR events key on the run ID and never wait on a queue. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} - cancel-in-progress: false - queue: max + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true env: ORIGIN_PORT: 8888 @@ -33,6 +34,9 @@ jobs: name: prepare integration artifacts runs-on: ubuntu-latest timeout-minutes: 30 + concurrency: + group: ${{ github.workflow }}-prepare-artifacts-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -70,6 +74,9 @@ jobs: needs: prepare-artifacts runs-on: ubuntu-latest timeout-minutes: 20 + concurrency: + group: ${{ github.workflow }}-integration-tests-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -129,6 +136,9 @@ jobs: needs: prepare-artifacts runs-on: ubuntu-latest timeout-minutes: 15 + concurrency: + group: ${{ github.workflow }}-integration-tests-fastly-ec-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -173,6 +183,9 @@ jobs: # once the pinned Playwright version supports the newer image. runs-on: ubuntu-24.04 timeout-minutes: 15 + concurrency: + group: ${{ github.workflow }}-browser-tests-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pbs-example.yml b/.github/workflows/pbs-example.yml index 2c1fb07303..d61072867a 100644 --- a/.github/workflows/pbs-example.yml +++ b/.github/workflows/pbs-example.yml @@ -17,21 +17,25 @@ on: permissions: contents: read -# Each pull request author runs one PR at a time per workflow, so one person -# opening many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled, and every job has a -# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and -# other events get their own group from the run ID and are never held back. +# A newer push to a pull request cancels that PR's superseded run. Each job +# then joins a per-author queue for that job (its own `concurrency` block), so +# one person opening many PRs runs one copy of each job at a time and cannot +# fill the runner pool ahead of everyone else. Queued jobs start in the order they began waiting; GitHub +# keeps up to 100 pending per queue and cancels arrivals past that. Every job +# has a timeout so a hung job cannot hold its queue for hours. Bot-authored +# PRs and non-PR events key on the run ID and never wait on a queue. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} - cancel-in-progress: false - queue: max + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true jobs: local-checks: name: PBS Terraform and Compose checks runs-on: ubuntu-latest timeout-minutes: 20 + concurrency: + group: ${{ github.workflow }}-local-checks-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max env: TF_IN_AUTOMATION: 'true' AWS_EC2_METADATA_DISABLED: 'true' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2e90e4b19..a5c669f83e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,21 +8,25 @@ on: branches: [main] pull_request: -# Each pull request author runs one PR at a time per workflow, so one person -# opening many PRs cannot fill the runner queue ahead of everyone else. Their -# other runs wait in order instead of being cancelled, and every job has a -# timeout so a hung run cannot hold that queue for hours. Bot-authored PRs and -# other events get their own group from the run ID and are never held back. +# A newer push to a pull request cancels that PR's superseded run. Each job +# then joins a per-author queue for that job (its own `concurrency` block), so +# one person opening many PRs runs one copy of each job at a time and cannot +# fill the runner pool ahead of everyone else. Queued jobs start in the order they began waiting; GitHub +# keeps up to 100 pending per queue and cancels arrivals past that. Every job +# has a timeout so a hung job cannot hold its queue for hours. Bot-authored +# PRs and non-PR events key on the run ID and never wait on a queue. concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} - cancel-in-progress: false - queue: max + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true jobs: test-rust: name: cargo test runs-on: ubuntu-latest timeout-minutes: 45 + concurrency: + group: ${{ github.workflow }}-test-rust-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -79,6 +83,9 @@ jobs: name: HTML EOF runtime (Fastly) runs-on: ubuntu-latest timeout-minutes: 25 + concurrency: + group: ${{ github.workflow }}-test-fastly-eof-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -128,6 +135,9 @@ jobs: name: cargo test (axum native) runs-on: ubuntu-latest timeout-minutes: 30 + concurrency: + group: ${{ github.workflow }}-test-axum-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -199,6 +209,9 @@ jobs: name: cargo check (cloudflare native + wasm32-unknown-unknown) runs-on: ubuntu-latest timeout-minutes: 15 + concurrency: + group: ${{ github.workflow }}-test-cloudflare-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -225,6 +238,9 @@ jobs: name: cargo check/build/test (spin native + wasm32-wasip1) runs-on: ubuntu-latest timeout-minutes: 20 + concurrency: + group: ${{ github.workflow }}-test-spin-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -263,6 +279,9 @@ jobs: name: cargo test (cross-adapter parity) runs-on: ubuntu-latest timeout-minutes: 15 + concurrency: + group: ${{ github.workflow }}-test-parity-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -289,6 +308,9 @@ jobs: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 45 + concurrency: + group: ${{ github.workflow }}-test-cli-${{ matrix.os }}-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max steps: - uses: actions/checkout@v4 @@ -354,6 +376,9 @@ jobs: name: vitest runs-on: ubuntu-latest timeout-minutes: 10 + concurrency: + group: ${{ github.workflow }}-test-typescript-${{ github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.login || github.run_id }} + queue: max defaults: run: working-directory: crates/trusted-server-js/lib From 600f6a9c19e4e2a9efbe59eab240290646aa0282 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Tue, 27 Oct 2026 21:46:40 +0530 Subject: [PATCH 4/4] Guard the CI concurrency blocks and job timeouts The required cargo fmt job now fails when a PR-triggered workflow lacks the per-PR cancellation block from test.yml, or when a job lacks timeout-minutes or a per-author queue keyed on pull_request.user.login with queue: max. Matrix jobs must also include the matrix in their group. Jobs that call a reusable workflow are skipped. The step uses the runner image's yq. Signed-off-by: dhruv8sh --- .github/workflows/format.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 1d03f47364..0379093897 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -64,6 +64,48 @@ jobs: steps: - uses: actions/checkout@v4 + # A hung job, a PR-triggered workflow without per-PR cancellation, or a + # job outside its per-author queue brings back the runner pile-up that + # the concurrency blocks prevent. Every PR-triggered workflow must carry + # test.yml's workflow-level block, and every job must set timeout-minutes + # and a per-author queue (keyed on the matrix too, for matrix jobs). + # Jobs that call a reusable workflow are skipped: they cannot set these. + - name: Require per-PR cancellation, per-author job queues and timeouts + shell: bash + run: | + reference=.github/workflows/test.yml + if [ "$(yq '.concurrency."cancel-in-progress" == true and (.concurrency.group // "" | test("pull_request\.number"))' "$reference")" != true ]; then + echo "::error file=$reference::test.yml must cancel superseded runs per pull request." + exit 1 + fi + expected=$(yq -o=json -I=0 '.concurrency' "$reference") + status=0 + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] || continue + on_pr=$(yq '.on | ((tag == "!!map" and has("pull_request")) or (tag == "!!str" and . == "pull_request") or (tag == "!!seq" and ([.[] | select(. == "pull_request")] | length > 0)))' "$f") + [ "$on_pr" = true ] || continue + if [ "$(yq -o=json -I=0 '.concurrency' "$f")" != "$expected" ]; then + echo "::error file=$f::PR-triggered workflows must use the workflow-level concurrency block from test.yml." + status=1 + fi + while read -r job; do + [ -n "$job" ] || continue + echo "::error file=$f::Job '$job' must set timeout-minutes." + status=1 + done < <(yq '.jobs | to_entries[] | select(.value.uses == null and .value["timeout-minutes"] == null) | .key' "$f") + while read -r job; do + [ -n "$job" ] || continue + echo "::error file=$f::Job '$job' must join a per-author queue: concurrency.group keyed on pull_request.user.login and queue: max." + status=1 + done < <(yq '.jobs | to_entries[] | select(.value.uses == null and (.value.concurrency.queue != "max" or (.value.concurrency.group // "" | test("pull_request\.user\.login") | not))) | .key' "$f") + while read -r job; do + [ -n "$job" ] || continue + echo "::error file=$f::Matrix job '$job' must include the matrix in its concurrency group so its legs do not queue behind each other." + status=1 + done < <(yq '.jobs | to_entries[] | select(.value.strategy.matrix != null and (.value.concurrency.group // "" | test("matrix\.") | not)) | .key' "$f") + done + exit "$status" + # Rust jobs must set up the toolchain through ./.github/actions/setup-rust, # which removes the image's stable and only saves caches from main. A # direct setup-rust-toolchain or rust-cache step skips both silently.