[mache-9ba2ed] fix(release): create a draft, attach assets, then publish - #617
Merged
Conversation
release.yml used softprops/action-gh-release with no `draft: true`. That
action creates and PUBLISHES the release BEFORE uploading any asset. Today
that is merely untidy; it becomes unrecoverable the moment tag protection is
enabled on this repo.
Protecting tags also makes GitHub treat releases on those tags as immutable,
and an immutable release rejects new assets ("Cannot upload asset ... to an
immutable release") and refuses to change state ("state cannot be changed when
release is immutable"). The tag is protected too, so it cannot be recut. The
result is a published, permanently EMPTY release.
Not hypothetical: signet lost v0.3.0-rc.3 exactly this way
(agentic-research/signet#170). There is no repo-level setting to check first —
`gh api repos/O/R --jq .immutable_releases` returns null either way; only
`gh release view <tag> --json isImmutable` reveals it. mache has no tag
ruleset today, which is the only reason this has not already bitten.
Draft-then-publish is right regardless of tag protection: a partial upload
failure leaves an unpublished draft rather than a half-populated release, and
publication becomes a deliberate act instead of a side effect of ordering.
Also fixes a second instance of the same defect class found while reading the
job. `release` is `if: always()` — required for the tag gate to evaluate — but
that also runs the job when `build` FAILED. With `fail-fast: false`, one
broken platform still leaves the other two artifacts downloadable and
`sha256sum mache-*` succeeds over whatever is present, so the workflow would
publish 2 of 3 binaries plus a checksum file silently attesting only those 2.
Sealing a partial release is as unrecoverable as sealing an empty one, so the
release now asserts all three platform binaries are present and fails loudly
otherwise.
Verified: actionlint clean, `task actions:lint` clean (all refs SHA-pinned),
and the guard exercised against all-three / one-missing / none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbcVvbUMkJHvSXbz1MMw5n
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.
release.ymlusedsoftprops/action-gh-releasewith nodraft: true. That action creates and publishes the release before uploading any asset.Today that is merely untidy. It becomes unrecoverable the moment tag protection is enabled on this repo.
Why it's unrecoverable, not just messy
Protecting tags also makes GitHub treat releases on those tags as immutable. An immutable release rejects new assets ("Cannot upload asset … to an immutable release") and refuses to change state ("state cannot be changed when release is immutable"). The tag is protected too, so it cannot be recut. The result is a published, permanently empty release.
Not hypothetical — signet lost
v0.3.0-rc.3exactly this way (agentic-research/signet#170).The trap is that nothing warns you first:
gh api repos/O/R --jq .immutable_releasesreturnsnullwhether or not it is in effect. Onlygh release view <tag> --json isImmutablereveals it, and by then the release is already sealed. mache has no tag ruleset today, which is the only reason this hasn't already bitten.Draft-then-publish is worth doing regardless: a partial upload failure leaves an unpublished draft rather than a half-populated release, and publication becomes a deliberate act instead of a side effect of upload ordering.
Second finding — same defect class, and this one bites today
releaseisif: always(), which is required for the tag gate to evaluate at all (a job withneedsplus anifis otherwise skipped when its dependency fails). But it also means the job runs whenbuildfailed.With
fail-fast: falseon the matrix, one broken platform still leaves the other two artifacts downloadable, andsha256sum mache-*succeeds over whatever is present. So the workflow would publish a release carrying 2 of 3 binaries, with a checksum file silently attesting only those 2.Sealing a partial release is exactly as unrecoverable as sealing an empty one. Unlike the ordering bug, this needs no tag ruleset to hurt — a flaky macOS runner is enough.
Added an explicit assertion that all three platform binaries are present, failing loudly otherwise. It's separable from the ordering fix if you'd rather handle it another way.
Verification
actionlintcleantask actions:lintclean — alluses:refs still SHA-pinned (mache-b8900d gate intact)task cipassed via the pre-push hookVerified by inspection and by exercising the guard — not by observing a release run, which would require cutting a tag. Stating that explicitly rather than implying broader coverage.
Deliberately not done here
Tag immutability is not enabled on mache as part of this. Doing both at once would make the first tag after the change the one that proves whether the fix works, with no recovery if it doesn't. Land this, cut a release normally, confirm assets attach, and only then consider the ruleset.
Found while auditing agentic-research public repos for tag-immutability safety.
rosaryhas the identical publish-ordering bug (rosary-9bd1de).🤖 Generated with Claude Code
https://claude.ai/code/session_01NbcVvbUMkJHvSXbz1MMw5n