Describe what should be investigated or refactored
GitHub's Immutable Releases feature is incompatible with Zarf's current release pipeline. The pipeline splits release creation (release-please) from asset upload (GoReleaser running later in release.yml) and relies on appending assets to an already-published release. Under immutable releases, once a release is published, assets cannot be added, modified, or deleted, and the associated tag is locked to its commit and cannot be reused, so the existing mode: append flow will fail the moment the repository (or zarf-dev org) opts in.
We should investigate restructuring the release pipeline so that:
- A single atomic publish step owns release creation. All artifacts (CLI binaries, init packages, SBOMs, checksums, agent image references) must be attached before the release transitions from draft → published.
- End-to-end validation runs before any tag is created. The current
validate-release job runs after release-please has already created the tag and GitHub Release. With immutable tags, a validation failure orphans the tag forever (cannot be deleted-and-reused), forcing every retry to consume a new version number.
mode: append in .goreleaser.yaml is reworked. GoReleaser currently appends the pre-built zarf-init-* artifacts to a release it does not own. Either GoReleaser should own release creation end-to-end (with init-packages produced inside its pipeline or staged as extra_files at create-time), or we drop GoReleaser's release responsibilities and use an alternative atomic-publish mechanism.
- release-please's role is scoped down. Options to evaluate:
- Configure release-please with
draft: true so it creates a draft release; have release.yml attach assets and flip draft → published as the final step.
- Have release-please only manage the changelog/version-bump PR and tag, with
release.yml owning GitHub Release creation entirely.
- Side-effects that occur before publish are reviewed for idempotency. The pipeline pushes the agent OCI image, signs it, and publishes init-packages to
ghcr.io/zarf-dev/packages before the GoReleaser step. Under the new flow, these still need to either complete successfully before the immutable publish or be safely re-runnable if the publish step fails.
Decisions to make:
- Which tool owns "publish" — release-please (with assets appended to a draft) or GoReleaser (release-please reduced to changelog/PR only)?
- Where does e2e validation move so it gates tag creation, not just asset upload?
Links to any relevant code
Additional context
GitHub Immutable Releases constraints (per docs and the GA changelog):
- Once published, a release's assets cannot be added, modified, or deleted.
- The associated git tag is locked to a specific commit, cannot be moved, and cannot be deleted while the release exists.
- Tag names cannot be reused even after deleting an immutable release.
- Existing releases remain mutable unless republished; only new releases become immutable when the feature is enabled at the repo or org level.
- GitHub's recommended pattern is create-as-draft → attach all assets → publish.
Today's Zarf flow violates each of the first three constraints:
| Step |
Current behavior |
Conflict |
| release-please merges PR |
Creates published GitHub Release + pushes tag |
Release is immutable from the moment it's published; nothing else can attach assets |
build-release job |
Builds binaries, agent image, init-packages |
Fine in isolation, but agent image push is not gated on validation |
validate-release job |
E2E tests run after tag/release exist |
Failure leaves a permanent, locked tag that cannot be reused |
create-release job |
GoReleaser --clean with mode: append |
Append-after-publish is rejected under immutable releases |
Supply-chain upside worth capturing as part of this work: immutable releases automatically generate a release attestation (cryptographic record of tag, commit SHA, and assets).
Sources:
Describe what should be investigated or refactored
GitHub's Immutable Releases feature is incompatible with Zarf's current release pipeline. The pipeline splits release creation (release-please) from asset upload (GoReleaser running later in
release.yml) and relies on appending assets to an already-published release. Under immutable releases, once a release is published, assets cannot be added, modified, or deleted, and the associated tag is locked to its commit and cannot be reused, so the existingmode: appendflow will fail the moment the repository (orzarf-devorg) opts in.We should investigate restructuring the release pipeline so that:
validate-releasejob runs after release-please has already created the tag and GitHub Release. With immutable tags, a validation failure orphans the tag forever (cannot be deleted-and-reused), forcing every retry to consume a new version number.mode: appendin .goreleaser.yaml is reworked. GoReleaser currently appends the pre-builtzarf-init-*artifacts to a release it does not own. Either GoReleaser should own release creation end-to-end (with init-packages produced inside its pipeline or staged asextra_filesat create-time), or we drop GoReleaser's release responsibilities and use an alternative atomic-publish mechanism.draft: trueso it creates a draft release; haverelease.ymlattach assets and flipdraft → publishedas the final step.release.ymlowning GitHub Release creation entirely.ghcr.io/zarf-dev/packagesbefore the GoReleaser step. Under the new flow, these still need to either complete successfully before the immutable publish or be safely re-runnable if the publish step fails.Decisions to make:
Links to any relevant code
mode: append(the direct blocker)extra_filesglob for./build/zarf-init-*appended post-publishdraftset today)Additional context
GitHub Immutable Releases constraints (per docs and the GA changelog):
Today's Zarf flow violates each of the first three constraints:
build-releasejobvalidate-releasejobcreate-releasejob--cleanwithmode: appendSupply-chain upside worth capturing as part of this work: immutable releases automatically generate a release attestation (cryptographic record of tag, commit SHA, and assets).
Sources: