Skip to content

ci: refuse to publish a version that is already on nuget.org - #43

Merged
elvogel merged 1 commit into
mainfrom
ci/guard-republish
Oct 4, 2026
Merged

elvogel merged 1 commit into
mainfrom
ci/guard-republish

Conversation

@elvogel

@elvogel elvogel commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

release.yml publishes with --skip-duplicate, so re-tagging an already-published version
succeeds while uploading nothing.
The run goes green, the release notes say the new version
shipped, and what sits on nuget.org is the older bytes. A published version can never be replaced
there — only unlisted — which makes publishing the one irreversible step in this workflow.

Until now the check was a human remembering to look. Cutting 0.2.5 I checked by hand, which is
the kind of guard that works right up until the once it doesn't.

Design

Runs before Restore, for the same reason the tag-matches-version check does: catch it before
anything is built, not after it is on nuget.org.

An unreachable nuget.org fails the step. A check that passes when it cannot verify anything is
worse than no check — the same stance phoenixmldb's check-pins.sh takes, and for the same
reason. A 404 on the package index is not that case: it means the package has never been
published, which is the correct state for a first release.

All four paths exercised against the live feed

path exit output
version already published (0.2.5) 1 names the version, says it cannot be replaced
version not published (0.2.6) 0 safe to publish
package never published (404 feed) 0 would be the first release
nuget.org unreachable 1 HTTP 000 · refusing rather than assuming it is new

Not reasoned about — run, with Directory.Build.props temporarily bumped for the second case and
a deliberately bad host for the fourth.

Known window

The flat-container listing lags a push by a few minutes — measured at roughly four minutes for
0.2.5
— so re-tagging inside that window still slips through. That is a far narrower hole than
the one this closes, and closing it completely would mean trusting the registration index, which
is slower still. Recorded in the step's own comment rather than left implicit.

🤖 Generated with Claude Code

https://claude.ai/code/session_018wZEgtuzaZPswykiGEBxbz

The publish step passes --skip-duplicate, so re-tagging an already-published
version SUCCEEDS while uploading nothing. The run goes green, the release notes
say the new version shipped, and what is on nuget.org is the older bytes. A
published version can never be replaced there, only unlisted, which makes
publishing the one irreversible step in this workflow.

Until now the check was a human remembering to look. Cutting 0.2.5 I checked by
hand, which is exactly the kind of guard that works until the once it does not.

Runs before Restore, for the same reason the tag-matches-version check does:
catch it before anything is built, not after it is on nuget.org.

An unreachable nuget.org is a FAILURE, deliberately. A check that passes when it
cannot verify anything is worse than no check. A 404 on the package index is not
that case -- it means the package has never been published, which is the correct
state for a first release.

All four paths exercised against the live feed rather than reasoned about:

  version already published (0.2.5)   exit 1, names the version and says why
  version not published (0.2.6)       exit 0
  package never published (404)       exit 0, "would be the first release"
  nuget.org unreachable               exit 1, "refusing rather than assuming"

Known window, recorded in the step's own comment: the flat-container listing
lags a push by a few minutes -- measured at roughly four for 0.2.5 -- so
re-tagging inside that window still slips through. That is a far narrower hole
than the one this closes, and closing it completely would mean trusting the
registration index, which is slower still.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wZEgtuzaZPswykiGEBxbz
@elvogel
elvogel merged commit b944a37 into main Oct 4, 2026
1 check passed
@elvogel
elvogel deleted the ci/guard-republish branch October 4, 2026 02:39
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