Skip to content

Push each package by path: pwsh does not expand the glob - #224

Merged
mgravell merged 1 commit into
mainfrom
fix-release-push
Sep 11, 2026
Merged

Push each package by path: pwsh does not expand the glob#224
mgravell merged 1 commit into
mainfrom
fix-release-push

Conversation

@mgravell

Copy link
Copy Markdown
Member

The 1.1.0 release got all the way to the push and then failed:

error: File does not exist (nupkgs/*.nupkg).

This job runs on windows-latest, where pwsh does not expand wildcards in arguments — so dotnet nuget push nupkgs/*.nupkg arrived at dotnet literally. The workflow this was modelled on runs on ubuntu-latest, where bash expands the glob first, which is why the line looked correct.

No dry run could have caught it: the push is gated on github.event_name == 'release', so it had never executed until there was a real release.

The fix

Enumerate and push by full path, so it doesn't depend on shell behaviour:

$packages = Get-ChildItem nupkgs/*.nupkg
if (-not $packages) { throw "no packages to push" }
foreach ($p in $packages) {
  dotnet nuget push $p.FullName --api-key ... --source https://api.nuget.org/v3/index.json --skip-duplicate
  if ($LASTEXITCODE -ne 0) { throw "push failed for $($p.Name)" }
}

Three deliberate details: it throws when there are no packages rather than silently pushing nothing (the same class of failure as the hidden-.nupkgs bug in #223, which was green-but-empty); it keeps --skip-duplicate so a partial push is safely re-runnable; and it checks $LASTEXITCODE per file, because a failing dotnet inside a foreach would not otherwise fail the step.

1.1.0 itself

Published by hand from the run artifact. The packages were already built correctly from the tagged commit, and re-running wouldn't have helped — a release event builds from the tag, so it would have rebuilt 0e071bf without this fix. Getting the fix into 1.1.0 would have meant retagging and re-deriving the offset, to avoid two manual pushes of packages CI had already validated.

The OIDC half now works: NuGet login succeeded on the re-run once NUGET_USER was the policy creator.

Also: the release procedure is written down

state-of-play.md gains a "Releasing" section — the steps, plus the two things that cost real time:

  • NUGET_USER is the policy creator, not the policy owner. Policies are created from your own nuget.org account with an owner dropdown; picking the Dapper org there scopes the policy to org-owned packages, but the token exchange looks up policies created by the username passed. Passing Dapper gives HTTP 401 … No matching trust policy owned by user.
  • versionHeightOffset is a fixed shift, not a pin — it drifts with every commit landing on main before the tag. Simulate a squash-merge rather than reasoning about it, since branch-local numbers mislead.

Version

No offset change. With 1.1.0 published, main should now move on to 1.1.1, which -4 gives naturally — simulated: {'VersionHeight': 5, 'BuildNumber': 1, 'NuGetPackageVersion': '1.1.1'}.

The 1.1.0 release got as far as the push and then failed with

    error: File does not exist (nupkgs/*.nupkg).

because this job runs on windows-latest, where pwsh does not expand wildcards in
arguments - so `dotnet nuget push nupkgs/*.nupkg` arrived literally. The workflow this
was modelled on runs on Linux, where bash expands the glob before dotnet sees it, which
is why the line looked fine. No dry run could catch it: the push is gated on the release
event, so it had never executed.

Now enumerates the files and pushes each by full path, which does not depend on the
shell, and fails loudly if there are none rather than silently pushing nothing. Kept
--skip-duplicate so a partial push stays safely re-runnable, and checks $LASTEXITCODE
per file, since a failing dotnet inside a foreach would not otherwise fail the step.

1.1.0 itself was published by hand from the run artifact - the packages were already
built correctly from the tagged commit, and re-running would have rebuilt from the tag
anyway, without this fix in it.

Also records the release procedure in state-of-play, including the two things that cost
real time: NUGET_USER must be the policy *creator* (not the org that owns the policy),
and versionHeightOffset is a fixed shift that drifts with every commit landing on main
before the tag.

No offset change: with 1.1.0 published, main should move on to 1.1.1, which -4 gives.
@mgravell
mgravell merged commit 93d99d3 into main Sep 11, 2026
2 checks passed
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