Skip to content

ci(release): verify the packages are on the feed, and republish as 1.0.0-beta.2 - #79

Merged
Reefact merged 3 commits into
mainfrom
claude/verify-published
Aug 12, 2026
Merged

ci(release): verify the packages are on the feed, and republish as 1.0.0-beta.2#79
Reefact merged 3 commits into
mainfrom
claude/verify-published

Conversation

@Reefact

@Reefact Reefact commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

v1.0.0-beta.1 was tagged, the release workflow went green, a GitHub release was created — and
nuget.org has neither package. dotnet nuget push --skip-duplicate exits 0 on any 409, and the
409 it received was not "you already sent this". This makes that failure impossible to miss the
next time, and prepares the republication as 1.0.0-beta.2.

Type of change

  • Bug fix
  • New feature
  • Breaking change to the public API
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

  • tools/packaging/verify-published.sh — asks the flat container for each id at the version just
    pushed, waits for indexing rather than for a status code (VERIFY_TIMEOUT, default 900s), and
    fails naming what is absent. It asks for the .nupkg rather than for index.json on purpose:
    the index lists the versions a package has, so a stale one answers 200 while saying nothing
    about the version in question.
  • tools/packaging/verify-published.test.sh — runs offline against a file:// tree laid out the
    way the flat container lays out its own, so both answers come from the code under test rather
    than from a stub of it. Six cases: neither package, only the main one, both, a version never
    pushed, no arguments, a version with no ids.
  • .github/workflows/release.yml — a Verify both packages are really on the feed step after the
    push, skipped on a dry run like the push itself.
  • .github/workflows/release.ymlCreate the GitHub release now looks first. One tag push
    produced two runs on 2026-08-12; the concurrency group queued the second rather than cancelling
    it (correctly — a release must not be killed midway), and it failed here, at the workflow's one
    non-repeatable step. An existing release now keeps its published notes and gets its assets
    uploaded with --clobber, which is what a genuine rerun after a partial failure needs.
  • .github/workflows/ci.yml — the new test joins the scripts job.
  • Both changelogs — the beta section is renamed to 1.0.0-beta.2 (nothing about the code changed
    between the two, so it is renamed rather than rewritten), and 1.0.0-beta.1 keeps a short
    section of its own saying what it turned out to be. A changelog that skips a version invites the
    reader to go looking for it, and somebody holding that tag or its GitHub release needs to be
    able to find out that the packages it describes were never installable.

--skip-duplicate itself stays, and is right: a rerun after a partial failure must be able to
send the package that did not go, and without the flag the first duplicate stops the command
before it reaches the second. What was missing is the question after it.

Testing

  • dotnet build -c Release — clean
  • dotnet test -c Release — 972 tests (109 + 613 + 58 + 192)
  • tests/PackageSmokeTest/run.sh
  • tools/packaging/verify-published.test.sh, tools/release-notes/changelog-section.test.sh,
    tools/style/lint-layout.test.sh and tools/style/lint-layout.sh
  • dotnet pack -p:Version=1.0.0-beta.2 produces the four files under their beta names, and
    verify-packages.sh accepts both
  • The extractor returns 417 lines for 1.0.0-beta.2, 5 for 1.0.0-beta.1, and exits 2 on
    Unreleased

The new checker is the one this repository cannot afford to take on trust — it runs once, at the
moment nothing can be taken back, and the failure it exists to catch is silent. So it was mutated
before being trusted: forcing is_published to return true fails three of the six assertions.

shellcheck is not installed here; both new scripts pass bash -n, and CI runs the real thing.

Public API

  • No change to the public surface
  • The surface changed and the baseline was updated in the same commit

Documentation

  • README / docs/ updated
  • The French counterpart was updated to match
  • CHANGELOG.md and docs/for-users/CHANGELOG.fr.md both updated
  • No documentation change required

Nine headings and sixty-five bullets on each side, so the structural comparison holds.

What this does not answer

The cause of the 409 is still unknown. Ruled out so far: no colliding or similarly-named package
on nuget.org, nothing in the account's Manage Packages, a trusted-publishing policy Active for the
right repository and workflow, and no earlier run that pushed — all thirteen release runs were
enumerated, and the dry run's Push step was skipped. The decisive datum would be the response
body from a push without --skip-duplicate, which this branch does not attempt: the flag is not
the bug, and removing it would trade a silent failure for a release that cannot be rerun.

What changes here is that the next attempt cannot report success without the packages being
retrievable. If 1.0.0-beta.2 meets the same 409, the release run goes red and says so.


Generated by Claude Code

claude added 3 commits August 12, 2026 13:35
`dotnet nuget push --skip-duplicate` exits 0 on any 409, and 409 is not only
"you already sent this". v1.0.0-beta.1 pushed both packages, was answered 409
Conflict on each, printed "already exists at feed", exited 0 — and nuget.org has
never held either package. Workflow green, GitHub release created, nothing
published. Every step said what it was supposed to say; nobody asked the feed.

The flag stays, because it is right: a rerun after a partial failure must be
able to send the package that did not go, and without it the first duplicate
stops the command before reaching the second. What was missing is the question
after it, so the release now asks the flat container for each package at the
version it just pushed, waits for indexing rather than for a status code, and
fails naming what is absent.

Asking for the .nupkg rather than for index.json is deliberate: the index lists
the versions a package has, so a stale one answers 200 while saying nothing
about this version. The test covers that case explicitly, along with the
half-published one a rerun exists for — an "any package answered" check calls
both a success.

The test runs offline against a file:// tree laid out the way the flat container
lays out its own, so "the package is there" and "it is not" are both answers
from the code under test rather than from a stub of it. curl's exit status
carries the verdict rather than %{http_code}, because file:// has no status line
and would leave the code at 000 for a file that is there.

A checker nobody checks is the failure it exists to catch, so it was mutated
before being trusted: forcing is_published to true fails three of the six
assertions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GcQ3umdkRNsR7FRB9s2P2w
One tag push produced two runs on 2026-08-12. The concurrency group queued the
second behind the first rather than cancelling it — correctly, since a release
must never be killed midway — and the second then failed at the one step that
could not be run twice: `gh release create` on a tag that already had a release.

Nothing was damaged and the run was red, which is the worst of the two: a red
release run reads as a release that did not happen, and the next person to look
cannot tell that from one that truly failed. It also blocks the case a rerun
exists for, where the push half-succeeded and everything after it must run
again.

So the step now looks first. An existing release keeps its notes — they are
published, and a rerun overwriting them would rewrite what readers already
have — and its assets are uploaded with --clobber, which is the part a genuine
rerun needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GcQ3umdkRNsR7FRB9s2P2w
The v1.0.0-beta.1 tag exists, its GitHub release exists, and nuget.org has
neither package. The `v*` ruleset forbids deletion and update, so that version
is spent whatever the cause, and the version it was meant to be is now
1.0.0-beta.2 — the section it wrote is renamed rather than rewritten, since
nothing about the code changed between them.

1.0.0-beta.1 keeps a section of its own rather than disappearing, because a
changelog that skips a version invites the reader to look for it. It says what
happened: tagged, pushed, refused, reported green, never installable. Somebody
holding the tag or the GitHub release needs to be able to find out that the
packages it describes were never on the feed, and the changelog is where they
will look.

Both languages move together, which the structural comparison enforces: nine
headings and sixty-five bullets on each side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GcQ3umdkRNsR7FRB9s2P2w
@Reefact
Reefact merged commit e3752c7 into main Aug 12, 2026
11 checks passed
@Reefact
Reefact deleted the claude/verify-published branch August 12, 2026 13:53
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.

2 participants