From 0255792451d7675754ffba1d9b3b669b550d0dc2 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 6 Jun 2026 13:42:50 +1000 Subject: [PATCH 1/2] ci: make crate publish re-runnable and fix cargo-sbom install on persistent cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled problems surfaced publishing 0.2.1 from the self-hosted runner: 1. `cargo install cargo-sbom --locked` exits 101 ("binary already exists") because the runner's CARGO_HOME (/cache/cargo) is a persistent volume — the binary survives across runs. Plain install is not idempotent there. Fix: add --force so it reinstalls the --locked pinned version each time. 2. When the publish job fails after release-please has already tagged the release, there was no way to re-publish: the job is gated on `release_created`, which is only true on the release-PR merge, and re-running the original run is pinned to the old workflow file. Fix: add a `workflow_dispatch` trigger and allow the publish job to run on it, so a tagged-but-unpublished version can be published via CI (preserving build-provenance + SBOM attestation) instead of by hand. --- .github/workflows/release.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2f5331..7722e00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ on: push: branches: - main + # Manual re-publish escape hatch: lets the publish job run for the version + # currently on main when an automated release published the tag but the + # publish job failed downstream (e.g. a transient Sigstore/Fulcio outage). + workflow_dispatch: {} permissions: contents: write @@ -29,7 +33,8 @@ jobs: publish: needs: release-please - if: ${{ needs.release-please.outputs.release_created }} + # Run on an automated release, OR on a manual dispatch (re-publish escape hatch). + if: ${{ needs.release-please.outputs.release_created || github.event_name == 'workflow_dispatch' }} runs-on: cachekit permissions: contents: read @@ -56,7 +61,11 @@ jobs: subject-path: target/package/*.crate - name: Install cargo-sbom - run: cargo install cargo-sbom --locked + # --force is required: the self-hosted runner's CARGO_HOME (/cache/cargo) is a + # persistent volume, so the binary survives between runs and a plain install + # exits 101 ("binary `cargo-sbom` already exists"). --force reinstalls the + # --locked pinned version idempotently. + run: cargo install cargo-sbom --locked --force - name: Generate SBOM run: | From 852727fe97e5ae40cad5de46fef5b28684013586 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 6 Jun 2026 13:47:14 +1000 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20harden=20release=20workflow=20per=20r?= =?UTF-8?q?eview=20=E2=80=94=20concurrency=20guard=20+=20main-only=20dispa?= =?UTF-8?q?tch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses CodeRabbit review on #41: - Add workflow-level concurrency (group: release-, cancel-in-progress: false) so a push and a manual dispatch can't race into two concurrent cargo publish attempts. - Restrict the publish job's manual-dispatch path to refs/heads/main so a workflow_dispatch fired from a feature branch can't publish that branch's Cargo.toml version. Also make the release_created check an explicit == 'true' comparison rather than relying on string coercion. --- .github/workflows/release.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7722e00..574a217 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,12 @@ permissions: contents: write pull-requests: write +# Serialise release runs so a push and a manual dispatch can't race into two +# concurrent cargo publish attempts. Never cancel an in-flight publish. +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: release-please: runs-on: cachekit @@ -33,8 +39,11 @@ jobs: publish: needs: release-please - # Run on an automated release, OR on a manual dispatch (re-publish escape hatch). - if: ${{ needs.release-please.outputs.release_created || github.event_name == 'workflow_dispatch' }} + # Run on an automated release, OR on a manual dispatch — but a manual dispatch + # must target main (workflow_dispatch can be fired from any ref; restricting to + # refs/heads/main stops a feature branch from publishing its own Cargo.toml). + # Explicit == 'true' avoids relying on string-coercion of the action output. + if: ${{ needs.release-please.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }} runs-on: cachekit permissions: contents: read