From 19d5ac65c00634253a0e89e6c4ce56fe34a0e84d Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Wed, 9 Sep 2026 14:27:14 +0800 Subject: [PATCH 1/2] ci(release): concurrency groups; shared verify-version action - The three core release workflows get a concurrency group with cancel-in-progress: false: one candidate at a time, one settle per version, one publish per release branch (on-release already has its per-tag group). Each ends in a push or a tag that must never be cancelled half-way, and none is atomic with its own guard. - The inline version gate in on-release becomes the shared release-verify-version action, with the debug-trace-server load check kept as its own step. Mirrors the org templates (megaeth-labs/.github#34). Main only: the in-flight release-v2.0.18 keeps the inline check from #209. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/on-release.yml | 37 +++++++++---------------- .github/workflows/release-candidate.yml | 6 ++++ .github/workflows/release-publish.yml | 6 ++++ .github/workflows/release-settle.yml | 6 ++++ 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index b6c893fe..a3a98c0b 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -69,30 +69,19 @@ jobs: - name: Build run: cargo build --release --locked --bin stateless-validator --bin debug-trace-server - - name: Verify the binaries - # stateless-validator carries a clap version (`#[clap(version)]`), so - # it is a gate: the built binary must report the tag's version, which - # also catches a tag / Cargo.toml mismatch nothing else checks here. - # debug-trace-server has no version flag; `--help` proves it loads and - # runs (a missing library or a crash fails the step) without gating. - run: | - set -euo pipefail - expected="stateless-validator ${TAG#v}" - actual="$(target/release/stateless-validator --version)" - if [[ "$actual" != "$expected" ]]; then - if [[ "$DRY_RUN" == "true" ]]; then - # A rehearsal reports what a real run would refuse, and carries on. - echo "::warning::built binary reports '$actual', expected '$expected' — a real release would stop here" - else - echo "::error::built binary reports '$actual', expected '$expected'; refusing to publish a mislabeled binary" - exit 1 - fi - fi - target/release/debug-trace-server --help > /dev/null - for b in stateless-validator debug-trace-server; do - echo "$b: $(stat -c %s target/release/$b) bytes" - done - echo "$actual" + # stateless-validator carries a clap version (`#[clap(version)]`), so + # it is a gate: the built binary must report the tag's version, which + # also catches a tag / Cargo.toml mismatch nothing else checks here. + - uses: megaeth-labs/.github/.github/actions/release-verify-version@main + with: + command: target/release/stateless-validator --version + version: ${{ env.TAG }} + dry_run: ${{ env.DRY_RUN }} + + - name: Check debug-trace-server runs + # No version flag to gate on; `--help` proves it loads and runs (a + # missing library or a crash fails the step, dry run or not). + run: target/release/debug-trace-server --help > /dev/null - uses: megaeth-labs/.github/.github/actions/release-assets@main with: diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 8c9e4c29..1d334f2b 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -17,6 +17,12 @@ on: types: [closed] branches: [main] +# One candidate at a time: two dispatches would race on the candidate branch, +# and a cut runs on its own merge and must never be cancelled half-way. +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: read diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 676e988c..344a9a54 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -14,6 +14,12 @@ on: types: [closed] branches: ["release-v*"] +# One publish per release branch: the tag guard and the tag push are two +# steps, so a re-run must queue behind a run in flight, never overlap it. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.base.ref }} + cancel-in-progress: false + permissions: contents: read diff --git a/.github/workflows/release-settle.yml b/.github/workflows/release-settle.yml index c552ab71..6f13dcc3 100644 --- a/.github/workflows/release-settle.yml +++ b/.github/workflows/release-settle.yml @@ -18,6 +18,12 @@ on: required: true type: string +# One settle per version: two dispatches for the same version would both +# force-push the same settle branch. Queue, never cancel. +concurrency: + group: ${{ github.workflow }}-${{ inputs.version }} + cancel-in-progress: false + permissions: contents: read From 7a95ec3ca29938ca2ee030fb917480429e20aadf Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Wed, 9 Sep 2026 14:41:16 +0800 Subject: [PATCH 2/2] ci(release): job-level concurrency for the pull_request-triggered jobs release-candidate and release-publish also start on every PR closing on their branch, and GitHub keeps one pending run per concurrency group, so the workflow-wide group let an unrelated closure evict a queued cut or publish. The groups now sit on the gated jobs (propose, cut, publish); a skipped job holds no slot. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/release-candidate.yml | 19 +++++++++++++------ .github/workflows/release-publish.yml | 13 +++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 1d334f2b..3a09fbb8 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -17,18 +17,20 @@ on: types: [closed] branches: [main] -# One candidate at a time: two dispatches would race on the candidate branch, -# and a cut runs on its own merge and must never be cancelled half-way. -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - permissions: contents: read jobs: propose: if: github.event_name == 'workflow_dispatch' + # One proposal at a time: two dispatches would race on the candidate + # branch. Job-level, not workflow-level: every PR closing on the default + # branch also starts this workflow, and a workflow-wide group would let + # that noise evict a queued release run (one pending run per group). + # A skipped job holds no slot in a job-level group. + concurrency: + group: ${{ github.workflow }}-propose + cancel-in-progress: false runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 @@ -63,6 +65,11 @@ jobs: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'chore/release-candidate-') && github.event.pull_request.user.login == 'mega-maxwell[bot]' + # One cut at a time, never cancelled: pull_request.closed does not refire. + # Job-level for the reason given on `propose`. + concurrency: + group: ${{ github.workflow }}-cut + cancel-in-progress: false runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 344a9a54..4ab64b42 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -14,12 +14,6 @@ on: types: [closed] branches: ["release-v*"] -# One publish per release branch: the tag guard and the tag push are two -# steps, so a re-run must queue behind a run in flight, never overlap it. -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.base.ref }} - cancel-in-progress: false - permissions: contents: read @@ -29,6 +23,13 @@ jobs: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'chore/release-settle-') && github.event.pull_request.user.login == 'mega-maxwell[bot]' + # One publish per release branch: the tag guard and the tag push are two + # steps, so a re-run must queue behind a run in flight, never overlap it. + # Job-level: other PRs closing on the release branch also start this + # workflow, and must not evict a queued publish (one pending per group). + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.base.ref }} + cancel-in-progress: false runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3