fix(release): make the GitHub release step idempotent so a failed publish can actually be retried - #15
Merged
Merged
Conversation
…lish can actually be retried `gh release create` fails with "a release with the same tag name already exists" on any re-run. Because that step sits BEFORE the PyPI publish, one publish failure was terminal: the release now existed, so every subsequent re-run died at creation and SKIPPED the publish entirely. The retry could not reach the thing it was retrying. v0.3.1 walked the whole trap. Attempt 1 created the release, then failed at PyPI with a 403 (Trusted Publisher mismatch). Attempts 2 and 3 both died at "Create GitHub release" -- so attempt 3 looked like a test of the publisher fix and tested nothing, because the publish step never ran. Only deleting the public release by hand let attempt 4 through, and it then published cleanly. Now: probe with `gh release view`, then `edit` + `upload --clobber` if it exists, `create` if not. `--clobber` because a re-run regenerates every artifact with fresh signatures, so replacing assets is the correct semantic rather than an error. `edit` takes `--prerelease=true|false` explicitly -- a bare `--prerelease` only ever SETS the flag, so a re-run could never demote a mis-marked pre-release. THE STEP ORDER IS UNCHANGED, DELIBERATELY. Publishing to PyPI first would look like the tidier fix and is worse: a PyPI upload burns that version number forever, while a GitHub release is deletable. Doing the reversible half first is exactly what made the v0.3.1 failure recoverable -- we could delete a release and retry, instead of holding an un-retryable PyPI version with no release. The defect was never the ordering; it was that the reversible half could not be repeated. The existing ordering test now says so, so nobody "fixes" it later. The step was renamed to "Create or update the GitHub release", which broke that ordering test's marker -- caught before pushing. Same shape as the `version==tag comparison` canary in the previous release commit: a test pinned to the exact WORDING of the thing it guards fails the moment the thing improves. Also corrects a comment in dependabot-lock-resync.yml asserting the App credentials were "configured correctly ... nothing to add". True of the PRIVATE repo; it did not survive the cutover. MEFORORG carried only MEFOR_FORBIDDEN_TOKENS, so the job would have skipped on every Dependabot uv PR while the comment claimed otherwise -- and it cost me a wrong assumption today. The credentials were added to MEFORORG on 2026-07-27. A configuration fact stated without the repository it applies to becomes a confident lie at the next migration. Mutation-verified both ways: neutralise the `gh release view` probe and the test fails; replace `--prerelease=false` with a bare `--prerelease` and it fails. Shell validated with `bash -n` on the YAML-de-indented run block.
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.
gh release createfails with "a release with the same tag name already exists" on any re-run. Because that step sits before the PyPI publish, one publish failure was terminal: the release now existed, so every re-run died at creation and skipped the publish entirely. The retry could not reach the thing it was retrying.v0.3.1 walked the whole trap
Attempt 3 looked like a test of the publisher fix and tested nothing.
The fix
Probe with
gh release view, thenedit+upload --clobberif it exists,createif not.--clobberbecause a re-run regenerates every artifact with fresh signatures — replacing assets is the correct semantic, not an error.--prerelease=true|falseexplicitly: a bare--prereleaseonly ever sets the flag, so a re-run could never demote a mis-marked pre-release.The step order is unchanged, deliberately
Publishing to PyPI first looks like the tidier fix and is worse. A PyPI upload burns that version forever; a GitHub release is deletable. Doing the reversible half first is exactly what made the v0.3.1 failure recoverable — we could delete a release and retry, rather than holding an un-retryable PyPI version with no release.
The defect was never the ordering; it was that the reversible half couldn't be repeated. The existing ordering test now says so, so nobody "fixes" it later.
A test pinned to wording, again
Renaming the step to "Create or update the GitHub release" broke the ordering test's marker — caught before pushing. Same shape as the
version==tag comparisoncanary in the previous release commit: a test pinned to the exact wording of the thing it guards fails the moment that thing improves. Second instance in two days.Also: a comment that became a lie at the cutover
dependabot-lock-resync.ymlasserted the App credentials were "configured correctly … nothing to add". True of the private repo; it didn't survive the move. MEFORORG carried onlyMEFOR_FORBIDDEN_TOKENS, so the job would have skipped on every DependabotuvPR while the comment claimed otherwise — and it cost me a wrong assumption today. Credentials added to MEFORORG 2026-07-27; the comment now names the repo.Verification
Mutation-verified both ways — neutralise the
gh release viewprobe and the test fails; swap--prerelease=falsefor a bare--prereleaseand it fails. Shell validated withbash -nagainst the YAML-de-indented run block.🤖 Generated with Claude Code