From 1f260df7d1ffbb940a311ae255229209543e19e6 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 6 Sep 2026 14:54:34 +0200 Subject: [PATCH] ci(concurrency): stop cancelling the run a merge is read against, and give the release a bracket Two different defects in two files, both about the same thing: which run survives. ci.yml cancelled unconditionally while also firing on a push to main and develop. That push run is the one a merge is read against, and an unconditional cancel throws it away when a second push lands behind it. What survives is a check that reported nothing, and a cancelled check reads as noise rather than as a failure -- so the merge ends up measured by an absence. The cancel now scopes to pull requests, where GitHub's per-job minute rounding actually bills for an answer about a tree that no longer exists. Outside a pull request the group only serialises, which costs nothing. release.yml had no bracket at all. It gets one that serialises and never cancels: the run is started by a v*.*.* tag push or by hand, both deliberate acts somebody is waiting on, and a cancelled release run is the worst case of the paragraph above. The fleet's usual form is `cancel-in-progress: ${{ github.event_name == 'pull_request' }}`, and it is deliberately NOT used in release.yml: that file has no pull-request trigger, so the expression would be false on every run it can have -- a line that reads as a live decision and is a constant. It is written as the constant instead, with the restoration named in the comment. Found by tools/workflow-concurrency-sweep.sh in claude-baseline. --- .github/workflows/ci.yml | 13 +++++++++++-- .github/workflows/release.yml | 18 +++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 491d7d55..eac932f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,16 @@ on: concurrency: group: ci-${{ github.ref }} - cancel-in-progress: true + # ⚠️ THE CONDITION IS THE LOAD-BEARING HALF. This workflow also fires on a push to `main` and + # `develop`, and THAT run is the one a merge is read against. An unconditional cancel throws it + # away when a second push lands behind it, leaving a check that reported NOTHING -- and a + # cancelled check reads as noise rather than as a failure. + # + # Outside a pull request the group then only SERIALIZES: two runs stay off one ref, for free. + # The cancel stays where it pays -- a pull request whose later push has overtaken the run in + # flight, which is where GitHub's per-job minute rounding actually bills for an answer about a + # tree that no longer exists. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: lint: @@ -110,4 +119,4 @@ jobs: | xcbeautify --renderer github-actions || exit $? else echo "::notice::No SpacemanTests scheme yet — skipping test run." - fi + fi \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2e67752..6149a482 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,22 @@ on: default: true type: boolean +concurrency: + # ⚠️ SERIALIZE, NEVER CANCEL -- this is a release. The run is started by a `v*` tag push or by + # hand, and both are deliberate acts somebody is waiting on the result of. A cancelled release + # run leaves a check that reported nothing, and a cancelled check reads as noise rather than + # as a failure, so the release ends up measured by an absence. + # + # The fleet's usual form is `cancel-in-progress: ${{ github.event_name == 'pull_request' }}`, + # and it is right wherever a pull request can start the workflow. It cannot here: `on:` carries + # a tag push and `workflow_dispatch` only, so that expression would be false on every run this + # file can have -- a line that reads as a live decision and is a constant. Written as the + # constant it would be, which is what this repository's siblings do in the same situation. + # + # The group still earns its place: two releases must not run against one ref at once. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + permissions: contents: write @@ -230,4 +246,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: sparkle-release-${{ env.RELEASE_VERSION }} - path: release-artifacts/ + path: release-artifacts/ \ No newline at end of file