fix(release-provenance): draft the release until its assets are attached - #30
Merged
Conversation
A published GitHub release is immutable once the repo has immutable
releases enabled, so whichever workflow publishes first locks every other
one out. release-provenance published the release as soon as it created
it, which is why drift-gate v0.2.0 shipped with no binaries: its separate
binaries.yml, racing on the same tag, got
HTTP 422: Cannot upload assets to an immutable release.
Create the release as a draft (drafts stay mutable) and publish it in a
final step, so it becomes immutable only once it is complete.
Add two inputs for callers that ship extra assets:
- assets-artifact: name of an artifact from an earlier job in the same
run, attached alongside the provenance. This is the one-writer path —
the caller builds binaries in a needs: job instead of in a second
tag-triggered workflow, so nothing races for the release. Needs
actions: read, declared here and required on the caller too, since a
called workflow only gets the intersection.
- finalize: pass false to leave the draft for another workflow to
publish. Documented as the weaker option — it moves the finish line
without ordering the two workflows.
Tests parse the workflow and assert creation is a draft and publishing
comes after the attach; both fail if the ordering is undone.
Refs bounded-systems/drift-gate#5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5vCUb3KDboKFJnFRpTwWg
| mkdir -p .mint-assets | ||
| # Fail loudly: a silent miss here ships a release MISSING the binaries | ||
| # it was asked to attach, which is the failure #19 is about. | ||
| gh run download "$GITHUB_RUN_ID" --name "${{ inputs.assets-artifact }}" --dir .mint-assets |
| # it was asked to attach, which is the failure #19 is about. | ||
| gh run download "$GITHUB_RUN_ID" --name "${{ inputs.assets-artifact }}" --dir .mint-assets | ||
| if [ -z "$(ls -A .mint-assets)" ]; then | ||
| echo "release-provenance: artifact '${{ inputs.assets-artifact }}' is empty" >&2 |
bdelanghe
marked this pull request as ready for review
August 13, 2026 05:17
bdelanghe
pushed a commit
to bounded-systems/drift-gate
that referenced
this pull request
Aug 13, 2026
A published GitHub release is immutable, so whichever workflow publishes it
first locks every other one out. binaries.yml and release.yml were both
triggered by the v* tag, and two tag-triggered workflows are unordered by
construction — so mint's release job published the release before binaries.yml
could attach, and the upload died with
HTTP 422: Cannot upload assets to an immutable release.
That is how v0.2.0 AND v0.3.0 both shipped with no binaries. Both workflows
were written create-or-upload to be "order-independent", which is correct for
mutable releases and precisely wrong for immutable ones — order-independence is
what puts the publish first half the time.
Fix it with one writer, the path mint's #30 added for exactly this: fold
binaries.yml into release.yml as a `needs:` job that uploads a workflow
artifact, and hand the artifact name to release-provenance via the new
`assets-artifact` input. It downloads it from the same run, attaches it next to
the provenance, and publishes once. Nothing races for the release.
Pin notes, since the pair looks mismatched on purpose:
- the workflow pin moves to mint main @ #30 (576e904), the commit carrying
the draft-until-attached fix. No mint tag contains it yet — v0.5.0 predates
it — so a SHA is the only way to consume it, which is what the org
convention asks for anyway.
- `ref` stays at v0.3.1. It selects the mint code that RUNS (`mint attest`),
and release-provenance.yml was byte-identical from v0.3.1 until #30, so
this change carries the ordering fix and nothing else. Moving `ref` would
put drift-gate's signed provenance onto three minors of unrelated mint
changes. version.yml is on v0.3.1 too; bump both when mint tags.
The binaries job keeps the workflow's default contents: read; only the release
job is granted contents/id-token write plus `actions: read`, which a caller has
to declare itself because a called workflow only gets the intersection.
release_workflow_test.ts guards the shape rather than a run, so a regression
fails on the PR instead of six weeks later on a tag that ships an empty and
now-immutable release. It asserts the `needs:`, that the uploaded artifact name
matches the one requested, that `actions: read` is granted, that no other
workflow writes to the release at all, and that the mint pin has not regressed
below the fix.
That last one is not hypothetical. Dependabot converges `uses:` pins onto the
referenced repo's LATEST TAG (.github/dependabot.yml spells this out), mint's
latest tag is v0.5.0, and v0.5.0 predates #30 and does not define
`assets-artifact` — so next Monday's grouped "actions" bump is a live path back
to this bug. The guard accepts a 40-hex SHA or a tag from v0.6.0 up, so it
blocks the revert without standing in the way of the real re-pin later.
Incidental: the v* trigger now also builds binaries for prerelease tags, which
binaries.yml's stricter v[0-9]+.[0-9]+.[0-9]+ filter excluded. release.yml
already ran provenance on v*, so this aligns them.
Does NOT backfill the v0.2.0/v0.3.0 binaries the issue also asks for: both
releases are already published and immutable, so nothing can be attached to
them. See the PR for the options — it needs a maintainer decision.
Refs bounded-systems/mint#30.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014P74tnmfWfpq5PaZ67BKsh
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.
Fixes #19.
The bug
A published GitHub release is immutable once the repo has immutable releases enabled, so whichever workflow publishes first locks every other one out.
release-provenance.ymlpublished the release the moment it created it — so drift-gate v0.2.0 shipped with no binaries: its separatebinaries.yml, racing on the same tag, gotBoth workflows were written create-or-upload to be "order-independent", which is correct for mutable releases and precisely wrong for immutable ones — order-independence is what puts the publish first half the time.
The fix
Create the release as a draft (drafts stay mutable) and publish it in a final step, so it becomes immutable only once it is complete.
Two new inputs for callers that ship extra assets:
assets-artifact— name of an artifact from an earlier job in the same run, attached alongside the provenance. This is the one-writer path: the caller builds binaries in aneeds:job rather than in a second tag-triggered workflow, so nothing races for the release at all. Fetched withgh run download "$GITHUB_RUN_ID"(a reusable workflow shares its caller's run id), which keeps the gh CLI already in use here and avoids pinning another action. Needsactions: read— declared here, and required on the caller too, since a called workflow only gets the intersection.finalize— passfalseto leave the draft for another workflow to publish. Documented as the weaker option: it moves the finish line without ordering the two workflows, soassets-artifactis the recommended path.Default behaviour is unchanged for the repos that attach nothing extra — they now get draft → publish within the one job.
Tests
Two tests parse the workflow as a fixture (the
release-cut.ymlallow-list test's shape) and assert creation carries--draftand that publishing comes after the attach — the ordering is the fix, and an ordering is what a later edit undoes without noticing.Both were mutation-checked: removing
--draft, and moving the publish ahead of the attach, each fail the suite. Full suite 20/20 green;mint planresolves the added intent to 0.5.0 → 0.6.0 (minor).Follow-up (not in this PR)
drift-gate#5 is the downstream half — apply the pattern there (fold
binaries.ymlintorelease.ymlas aneeds:job feedingassets-artifact) and backfill the v0.2.0 binaries. That needs this merged and a mint tag to pin first.Generated by Claude Code