Skip to content

[mache-9ba2ed] fix(release): create a draft, attach assets, then publish - #617

Merged
jamestexas merged 1 commit into
mainfrom
fix/release-draft-then-publish
Aug 7, 2026
Merged

[mache-9ba2ed] fix(release): create a draft, attach assets, then publish#617
jamestexas merged 1 commit into
mainfrom
fix/release-draft-then-publish

Conversation

@jamestexas

Copy link
Copy Markdown
Contributor

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.

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.3 exactly this way (agentic-research/signet#170).

The trap is that nothing warns you first: gh api repos/O/R --jq .immutable_releases returns null whether or not it is in effect. Only gh release view <tag> --json isImmutable reveals 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

release is if: always(), which is required for the tag gate to evaluate at all (a job with needs plus an if is otherwise skipped when its dependency fails). But it also means the job runs when build failed.

With fail-fast: false on the matrix, one broken platform still leaves the other two artifacts downloadable, and sha256sum 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

  • actionlint clean
  • task actions:lint clean — all uses: refs still SHA-pinned (mache-b8900d gate intact)
  • task ci passed via the pre-push hook
  • guard logic exercised standalone against all three cases:
    all three:      PASS — all 3 present
    darwin missing: REFUSE — missing: mache-darwin-arm64
    none:           REFUSE — missing: mache-darwin-arm64 mache-linux-amd64 mache-linux-arm64
    

Verified 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. rosary has the identical publish-ordering bug (rosary-9bd1de).

🤖 Generated with Claude Code

https://claude.ai/code/session_01NbcVvbUMkJHvSXbz1MMw5n

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
@jamestexas
jamestexas merged commit ececdc3 into main Aug 7, 2026
18 checks passed
@jamestexas
jamestexas deleted the fix/release-draft-then-publish branch August 7, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant