From fa659b1cd6561e81a41bb304237c4b0c7afb5dbf Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Wed, 9 Sep 2026 00:36:19 +0300 Subject: [PATCH] ci: let the push decide the publish, and drop the check that cannot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first real release proved the verification step wrong. It polled the flat container for ten minutes and failed the run, while the push had in fact succeeded: nuget.org accepted both packages at 21:24 and made them restorable shortly after the poll gave up at 21:34. The bound was not the mistake — the idea was. Nothing anonymous reflects an accepted package before validation finishes: not the flat container, the registration, the search index, or the package page. Only the signed-in owner sees it, marked "Validating", and nuget.org's own guidance allows up to an hour. A poll therefore cannot separate "accepted, validating" from "refused" inside any budget worth a runner, which is the single distinction it existed to draw. Nor is post-push verification a practice this ecosystem follows. The defect it was built for is closed where it happens instead. Dropping --skip-duplicate makes a 409 fail the push itself, immediately, carrying the gallery's own message — including the ID prefix reservation that once passed as success across two dispatches. Retrying a dispatch after a partial push now aborts on the package that already landed, which is a decision for whoever dispatched it rather than something to swallow. The GitHub Packages lane keeps the flag: a duplicate there is benign, that registry has no reserved namespaces, and its versions can be deleted. --- .github/workflows/publish-nuget.yml | 51 +++-------------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index f34bf537f..bfce166c0 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -99,11 +99,11 @@ jobs: # dotnet nuget push implements its own glob expansion; the path is quoted so the shell does # not expand it first (two packable projects produce two .nupkg files here). # - # --skip-duplicate reports any 409 as "already exists" and exits zero, which is how two - # dispatches went green while every push was in fact refused by an ID prefix reservation. The - # flag stays, because retrying a dispatch after a partial push must not abort on the package - # that already landed; what changes is that it no longer decides success on its own. The - # verification below does, and it separates the two outcomes the flag conflates. + # No --skip-duplicate on this lane. The flag reports any 409 as "already exists" and exits + # zero, which is how two dispatches went green while every push was refused by an ID prefix + # reservation. Without it a refusal fails here, immediately, carrying the gallery's own + # message. The cost is that retrying a dispatch after a partial push aborts on the package + # that already landed — which is the operator's decision to make, not a thing to swallow. - name: Push to NuGet.org if: inputs.target == 'nuget' env: @@ -112,47 +112,6 @@ jobs: dotnet nuget push "packages/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key "$NUGET_KEY" - --skip-duplicate - - # A push that exits zero is not a publication. This asks the flat container for the exact - # versions the pack step produced: a package that genuinely already existed answers yes, a - # package the gallery refused answers no. A newly accepted version appears only once - # validation completes, so the check waits rather than reading the registry once. - - name: Verify NuGet.org publication - if: inputs.target == 'nuget' - shell: pwsh - env: - PACKAGE_VERSION: ${{ inputs.version }} - run: | - $version = $env:PACKAGE_VERSION - $suffix = ".$version.nupkg" - $packages = Get-ChildItem packages | Where-Object { $_.Extension -eq '.nupkg' } - if ($packages.Count -eq 0) { throw "No .nupkg files were produced, so there is nothing to verify." } - $ids = $packages | ForEach-Object { - if (-not $_.Name.EndsWith($suffix)) { throw "Package file '$($_.Name)' does not carry the dispatched version '$version'." } - $_.Name.Substring(0, $_.Name.Length - $suffix.Length) - } - $wanted = $version.ToLowerInvariant() - $deadline = (Get-Date).AddMinutes(10) - while ($true) { - $missing = @() - foreach ($id in $ids) { - $url = "https://api.nuget.org/v3-flatcontainer/$($id.ToLowerInvariant())/index.json" - $response = Invoke-WebRequest -Uri $url -SkipHttpErrorCheck -Headers @{ 'Cache-Control' = 'no-cache' } - $published = $response.StatusCode -eq 200 -and - (($response.Content | ConvertFrom-Json).versions -contains $wanted) - if (-not $published) { $missing += $id } - } - if ($missing.Count -eq 0) { - Write-Host "Published on NuGet.org: $($ids -join ', ') $version" - break - } - if ((Get-Date) -ge $deadline) { - throw "Still absent from NuGet.org after 10 minutes: $($missing -join ', ') $version. The push reported success, so check the gallery for a refused or still-validating upload." - } - Write-Host "Awaiting NuGet.org validation: $($missing -join ', ')" - Start-Sleep -Seconds 20 - } - name: Push to GitHub Packages if: inputs.target == 'github'