From bcd00b510811159f01a91a7afb3958127d6227f1 Mon Sep 17 00:00:00 2001 From: Greg von Nessi Date: Thu, 20 Aug 2026 11:52:19 +0100 Subject: [PATCH] Cap what a single fork pull request can spend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This workflow is reachable from a fork pull request and had neither a concurrency group nor a job timeout, so it inherited GitHub's defaults for both: unlimited concurrent runs, and **six hours** per job. On paid Blacksmith runners that is a real number rather than a theoretical one — 6 job runs at the six-hour ceiling is 36 compute-hours for a single push, and without a concurrency group a contributor pushing five times held five complete sets at once rather than superseding the previous one. The repo now requires approval for every external contributor's fork PR, so nothing external starts without a deliberate click. This is the second half: the approval gate decides *who* runs, and these two settings decide *how much* a run that has been approved can cost. It bounds our own pushes too, which is where most of the waste actually was. Sized against measurement rather than a guess. Over the last six runs the slowest job here is 30s; 20 minutes leaves generous headroom while turning the worst case from 36 compute-hours into about 2. A job that hits 20 minutes is not a slow job, it is a stuck one. --- .github/workflows/ci.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b6c66c..655f02d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,14 @@ on: pull_request: branches: [main] +concurrency: + # A new push to the same PR or branch supersedes the run in flight. Without + # this every push queued a *fresh* full set of jobs alongside the old ones, + # so a branch pushed five times held five complete matrices at once — on + # paid Blacksmith runners, and with nothing reclaiming the superseded work. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 @@ -14,6 +22,12 @@ jobs: test: name: Test runs-on: blacksmith-2vcpu-ubuntu-2404 + # Capped because the GitHub default is **six hours** per job, and this + # workflow is reachable from a fork pull request. Measured over the last + # six runs the slowest job here is 30s, so 20 minutes is roughly 40x + # headroom while removing the runaway: a stuck or hostile job now costs + # 20 minutes of a paid runner rather than a working day of one. + timeout-minutes: 20 strategy: matrix: features: ["", "--all-features", "--no-default-features"] @@ -35,6 +49,7 @@ jobs: lint: name: Lint runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 20 steps: - uses: actions/checkout@v6 @@ -55,6 +70,7 @@ jobs: docs: name: Documentation runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 20 steps: - uses: actions/checkout@v6 @@ -72,6 +88,7 @@ jobs: msrv: name: MSRV (1.93) runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 20 steps: - uses: actions/checkout@v6