Skip to content

ci(publish): make the PyPI upload idempotent so a Release cannot go red over a duplicate - #57

Merged
vdavez merged 1 commit into
mainfrom
fix/publish-idempotent
Aug 4, 2026
Merged

ci(publish): make the PyPI upload idempotent so a Release cannot go red over a duplicate#57
vdavez merged 1 commit into
mainfrom
fix/publish-idempotent

Conversation

@makegov-mark

@makegov-mark makegov-mark Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Problem

publish.yml runs a bare uv publish, which fails the run when a file is already present on the index. So any version that reaches PyPI by another route — a manual uv 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

-      run: uv publish
+      run: uv publish --check-url https://pypi.org/simple/

--check-url has 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.

…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
vdavez merged commit 83c3003 into main Aug 4, 2026
11 checks passed
@vdavez
vdavez deleted the fix/publish-idempotent branch August 4, 2026 11:23
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>
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