fix(release): publish the release only once it is signed - #4
Merged
sydorovdmytro merged 1 commit intoAug 21, 2026
Conversation
sydorovdmytro
force-pushed
the
dmytrosydorov/devops-1386-fixsemstat-reconcile-the-release-pipeline-with-immutable
branch
from
August 21, 2026 09:59
1ca123f to
61d928d
Compare
Release immutability is on for this repository, and it engages when a release is published. goreleaser published first and uploaded after, so a signing outage could freeze a release with no provenance and the documented repair, a re-dispatch on the tag, could no longer replace anything. The release is now cut as a draft and published as the last step of the job, after signing and attestation. A failed run leaves a deletable draft, a re-dispatch repairs it, and a run that finds the tag already published refuses to start rather than moving the image tags first and failing on the release afterwards. Two details make that repair actually work. goreleaser resolves an existing release by tag through an endpoint that returns published ones only, so without replace_existing_draft it cannot see the draft its own failed run left behind and adds a second one for the tag. And gh resolves a tag by racing a published lookup against a draft one, so the publish step takes an id instead, which cannot pick an older unsigned draft over this run's. Since goreleaser matches drafts on release name alone, the preflight also refuses a draft it would fail to replace, and the name template is pinned rather than left to its default.
sydorovdmytro
force-pushed
the
dmytrosydorov/devops-1386-fixsemstat-reconcile-the-release-pipeline-with-immutable
branch
from
August 21, 2026 10:02
61d928d to
41e9ae0
Compare
sydorovdmytro
deleted the
dmytrosydorov/devops-1386-fixsemstat-reconcile-the-release-pipeline-with-immutable
branch
August 21, 2026 10:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
release.draft: true, andrelease.yamlpublishes it as the last step of the release job, after signing and attestation. Immutability engages at publish, so publishing last is what keeps a release from being frozen without its provenance.gh release edit <tag>resolves a tag by racing a published lookup against a draft one, so with more than one draft on the tag it can publish an older, unsigned draft and leave this run's behind.replace_existing_draftadded: goreleaser resolves an existing release by tag through an endpoint that returns published releases only, so without it a re-dispatch never sees the draft its own failed run left behind and adds a second one.replace_existing_artifactsstays for the in-run 422 retry on a landed-but-reported-failed upload.mode: replacedropped as unreachable.release.name_templatepinned, since goreleaser's draft cleanup and the publish step both key on the release name.verifyasserts.immutable == true.GET /repos/{owner}/{repo}/immutable-releasesreports the repository setting directly but needs admin read, which noGITHUB_TOKENpermission grants, so a published release stays the only place an Actions run can see it.Two trade-offs, both documented:
verifyruns after publish, so a verify failure needs a new tag rather than a re-dispatch; and goreleaser pushes the Homebrew cask while the release is still a draft, so its URL 404s until the publish step runs.Test plan
actionlintonrelease.yaml, shellcheck included: cleanzizmor: no findingsgoreleaser checkagainst the pinned v2.17.0 binary: passesshellcheck -xon the new run blocks, extracted verbatim: clean--paginatehandling checked against a merged array, per-page arrays,[], and no outputgh apipipelines confirmed to fail closed underset -euo pipefail, so a failed listing cannot pass the guardgo build ./... && go test ./...: passimmutable=truegets confirmedCloses DEVOPS-1386