From 1ac60b027748469443b509563d13138e1795fdd3 Mon Sep 17 00:00:00 2001 From: Greg von Nessi Date: Thu, 20 Aug 2026 12:06:03 +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. Counting the matrix that is 3 job runs per push, so the ceiling was 3x6 = 18 compute-hours for one push — and with no concurrency group a contributor pushing five times held five complete sets at once rather than superseding the previous one. Unlike the Blacksmith repos this one runs on hosted minutes and the repo is public, so those hours are free rather than billed. The reason to bound it anyway is that "free" describes the invoice and not the behaviour: an unbounded job is still a job nobody can tell from a hung one, and the repository-level protection this pairs with — approval now required for every external contributor, not merely first-timers — is only half a control if an approved run has no ceiling. Sized against measurement. Over the last six runs the slowest job here is 2m07s, so 20 minutes is generous headroom while removing the runaway. A job that hits 20 minutes is not a slow job, it is a stuck one. --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31cfe28..7118f74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,10 +5,24 @@ on: branches: [main] pull_request: +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 + jobs: check: name: fmt + clippy + test (${{ matrix.os }}) runs-on: ${{ matrix.os }} + # 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 2m07s, so 20 minutes is roughly 9x + # 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: fail-fast: false matrix: