ci(publish): make the PyPI upload idempotent so a Release cannot go red over a duplicate - #57
Merged
Merged
Conversation
…ed over a duplicate `uv publish` fails the run when a file is already on the index, so any version that reached PyPI by another route — a manual `uv publish`, a re-run of this workflow, a partial upload — makes the GitHub Release that triggers it go red even though the package is fine and nothing needs doing. That is exactly what happened with 1.5.0: it was published by hand before the Release existed, which left cutting the Release as a choice between skipping it (losing the tag and notes every prior version has) and accepting a failed run attached to it. `--check-url` has uv query the index first and skip files already present, so the step is a no-op instead of an error. Re-running the workflow is now always safe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vdavez
added a commit
that referenced
this pull request
Aug 4, 2026
…he sdist hash The sdist whitelist included `/.github/workflows`, so any commit touching CI changed the sdist for an otherwise-unchanged package. That defeats `uv publish --check-url`, which compares the freshly-built file against the index and errors on a mismatch — so a version already on PyPI stops being recognizable as already-published. v1.5.0 hit exactly this: published by hand from the release commit, then #57 added `--check-url` to publish.yml, a workflow-only change that altered the sdist because the workflow ships inside it. The Release's publish run skipped the wheel and failed on the sdist hash. CI definitions are not buildable-from-source content, so the entry bought nothing and cost hash stability. Also trims the comment #57 left on the publish step, and keeps the note here to one line. Verified: appending a line to `.github/workflows/lint.yml` and rebuilding now yields an identical sha256, where before the hash moved. `uv build` still builds the wheel from the sdist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem
publish.ymlruns a bareuv publish, which fails the run when a file is already present on the index. So any version that reaches PyPI by another route — a manualuv publish, a re-run of this workflow, a partial upload that got half the artifacts up — makes the GitHub Release that triggered it go red, even though the package is correct and there is nothing to fix.That is not hypothetical: 1.5.0 is already on PyPI, published by hand before the Release existed. That left cutting the v1.5.0 Release as a choice between two bad options — skip it (losing the tag and notes that every prior version has) or accept a permanently failed workflow run attached to it.
Fix
--check-urlhas uv query the index before uploading and skip files already there, so the step becomes a no-op instead of an error.Why this is the right shape
The workflow becomes idempotent, which is the property you actually want from a publish step: re-running it is always safe, and the Release's green check means "this version is on PyPI" rather than "this workflow happened to be the thing that put it there." Those are different claims, and the second one is not worth failing a release over.
It also decouples the two jobs the Release is doing. Publishing the artifact and recording the version (tag + notes + changelog anchor) are separate concerns, and a manual publish should not be able to block the record.
After this merges
v1.5.0 can be tagged and released cleanly — the publish step will run, find both files already on PyPI, skip them, and pass.