Skip to content

🪲[Bug]: Publish Module is failing #528

Description

Describe the bug

When the CI run, after checking all your changes, fixing BC in my repo I can still see this

 Publish module to PowerShell Gallery using API key from environment.
    Find-PSResource: /home/runner/work/PSWEE/PSWEE/_wf/.github/actions/Publish-PSModule/src/publish.ps1:148
    Line |
     148 |  … edPackage = Find-PSResource -Name $name -Version $publishPSVersion -R …
         |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         | Package with name 'PSWEE', version '1.3.1' could not be found in
         | repository 'PSGallery'.
    Error: Process completed with exit code 1.

For me there is an issue on the code on src/publish.ps1. Since every brand-new version is, by definition, not yet on the Gallery it cannot proceed. And due to -ErrorAction Stop we cannot go further.

In .github/actions/Publish-PSModule/src/publish.ps1, region "Publish to PSGallery":

$publishedPackage = Find-PSResource -Name $name -Version $publishPSVersion -Repository PSGallery -ErrorAction Stop
if ($publishedPackage) {
    # "already published" / resume notice
} else {
    Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey $psGalleryApiKey
}

Version information

NA

To reproduce

Push something on a repo using PSModule

Code snippet

NA

Relevant output

NA

Activity

  1. MariusStorhaug commented on Sep 1, 2026

    @MariusStorhaug
    Member

    Arnaud CHARLES (@arnaudcharles) Checking it now! Thank you for the patience after all those changes :)
    Just did some heady refactoring after seeing how to build agents and having the possibility of colocating actions into a reusable workflow from the same repo. Will validate the changes and check the compatibility ASAP.

  2. MariusStorhaug commented on Sep 1, 2026

    @MariusStorhaug
    Member

    Reviewed the scenario end to end against the PSWEE runs. There are two defects here; #529 fixes one of them.

    Defect 1 — the crash (correctly diagnosed in this issue)

    publish.ps1:148 used Find-PSResource ... -ErrorAction Stop. PSResourceGet raises a terminating PackageNotFound (Microsoft.PowerShell.PSResourceGet.UtilClasses.ResourceNotFoundException) when the version is absent, so the existence probe introduced in #512 breaks every first-time publication, exactly as described.

    Verified locally: the regression test added in #529 fails against main and passes with the one-line change. #529 fixes the reported bug.

    One nit on the fix: -ErrorAction SilentlyContinue swallows all errors, not just "not found". During a transient Gallery outage $publishedPackage is null, publish is attempted anyway, and if the version does already exist Publish-PSResource fails with a 409 — losing the resume behaviour the probe exists to provide. Narrowing to the PackageNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource error ID keeps the original intent.

    Defect 2 — the wrong version was being published

    Correct version 1.4.0
    Version attempted 1.3.1

    PSWEE PR #12 carries the Minor label and merged at 19:46:58 as f8c59e6. Latest GitHub release and Gallery version were both 1.3.0, so the resolved version should have been 1.4.0. The Plan job resolved 1.3.1 via a patch bump.

    Cause chain:

    1. The push run for f8c59e6 (run 33551501626) was cancelled one second after start. PSWEE's caller still uses the pre-v8 template: group: ${{ github.workflow }}-${{ github.ref }} with cancel-in-progress: true, and no unlabeled type. The v8 contract requires cancel-in-progress: false and the github.event.pull_request.number || github.ref key precisely so a release-capable push is never interrupted.
    2. Recovery was attempted as a workflow_dispatch on main. In Get-PSModuleSettings/src/main.ps1:245, PR association for a commit is gated on if ($isPush -and $commitSha). A manual dispatch therefore yields Context.PullRequest = null, Resolve-PSModuleVersion logs "Using direct default-branch release context with the default patch bump", and the Minor label is silently discarded.

    Consequence: even with #529 merged, that dispatch would publish 1.3.1 instead of 1.4.0 — and 1.3.1 would then permanently occupy the Gallery, since Gallery versions cannot be reclaimed.

    What should have happened

    • The merge push to main should have run to completion and published 1.4.0, because the associated merged PR carries the Minor label.
    • The Gallery lookup for a brand-new version should be a non-fatal "not found" and proceed to publish.
    • A workflow_dispatch recovery on the default branch should resolve the merged PR for HEAD and honour its version label, or refuse to release — rather than silently downgrading to Patch.

    Suggested follow-ups

    1. Merge 🪲 [Fix]: New module versions publish to the PowerShell Gallery #529, ideally with the error filter narrowed to PackageNotFound.
    2. Track defect 2 separately: workflow_dispatch on the default branch must resolve the merged PR for the dispatched SHA and honour its label, or fail loudly.
    3. Arnaud CHARLES (@arnaudcharles) — PSWEE's caller workflow needs the v8 concurrency block (cancel-in-progress: false, PR-or-ref key, and the unlabeled PR type). The merge push being cancellable is what forced the manual recovery in the first place. See Calling the workflow.
  3. arnaudcharles commented on Sep 1, 2026

    @arnaudcharles
    Author

    Thanks Marius Storhaug (@MariusStorhaug) for the quick answer.

    Regarding PSWEE I did my first try with 1.3.1 but ended up moving to 1.4.0 as it make more sense. This is why copilot found an irregularity.
    I will wait the publication of the fix and try again on 1.4.0

  4. MariusStorhaug commented on Sep 2, 2026

    @MariusStorhaug
    Member

    Marius Storhaug (@MariusStorhaug) Applied the review nit on #529, and the narrowing exposed that the test harness could not actually observe the behaviour it asserted.

    1. Probe narrowed to PackageNotFound

    -ErrorAction SilentlyContinue swallowed every lookup failure. The probe now runs with -ErrorAction Stop and only treats the PackageNotFound error ID as "not yet published":

    $publishedPackage = $null
    try {
        $publishedPackage = Find-PSResource -Name $name -Version $publishPSVersion -Repository PSGallery -ErrorAction Stop
    } catch {
        if ($_.FullyQualifiedErrorId -notlike 'PackageNotFound,*') { throw }
        Write-Host "$name $publishPSVersion is not on the PowerShell Gallery yet."
    }

    Verified against the live Gallery — the two cases are cleanly distinguishable by error ID:

    Scenario FullyQualifiedErrorId
    Absent version (PSSemVer 99.99.99) PackageNotFound,…FindPSResource
    Absent module PackageNotFound,…FindPSResource
    Unreachable repository (outage) HttpRequestCallFailure,…FindPSResource

    The PackageNotFound,* prefix match keeps this robust if the cmdlet name in the ID ever changes.

    2. The recovery tests were not testing anything

    Three defects in the harness, all found while validating the narrowed fix:

    The assertions were vacuous. Publish-PSResource was shimmed to set $script:publishInvoked = $true, but publish.ps1 runs via & in its own scope, so the flag never propagated back. $script:publishInvoked stayed $false unconditionally — so Should -BeFalse passed whether or not publish ran. Replaced with a hashtable captured by GetNewClosure(), which is shared by reference and does propagate. (Note GetNewClosure() captures locals, so the collector must be assigned unqualified before being exposed as $script:.)

    The marker-file variant raced. The other case wrote a marker under $env:GITHUB_WORKSPACE. Environment variables are process-wide and Pester's parallel runspaces share one process, so the marker landed in another test file's TestDrive:

    DIAG marker-target=/private/tmp/Pester_icli/workspace/publish-invoked
    DIAG expected=/private/tmp/Pester_ptrc/workspace/publish-invoked
    

    The not-found shim could not detect the bug. It used $PSCmdlet.ThrowTerminatingError(...), which ignores -ErrorAction entirely — unlike the real cmdlet. That shim throws under Stop and SilentlyContinue, so it could not tell the fix from the defect. Now uses Write-Error with the real PackageNotFound error ID, which honours -ErrorAction exactly as PSResourceGet does.

    Added a third case asserting a non-PackageNotFound failure stays fatal and does not publish. Each test is confirmed to fail against the specific defect it guards:

    Implementation Test that catches it
    -ErrorAction Stop (original bug) publishes when the resolved version is not in the Gallery
    -ErrorAction SilentlyContinue (the nit) fails without publishing when the Gallery lookup errors for another reason
    Narrowed to PackageNotFound all pass

    3. Unrelated: Test-Actions was red on this branch

    The 4 Get-NextPrereleaseNumber failures were test pollution, not a version-resolution defect. Remove-Item -Path function:global:X silently no-ops — global: is a scope qualifier, not a valid provider path segment, and -ErrorAction SilentlyContinue hid the failure. The Find-PSResource shim therefore survived AfterAll and shadowed the real cmdlet for later test files, so Get-NextPrereleaseNumber splatted Prerelease = $true into the two-parameter shim. Same latent bug fixed for the gh and git shims in Release-PSModule.WhatIf.Tests.ps1.

    Worth a separate issue: Test-Actions never runs in parallel

    .github/workflows/Test-Actions.yml builds a config with Run.Parallel/Shuffle, asserts the options applied, then discards it and creates a fresh New-PesterConfiguration for the actual run. Parallel and shuffle are validated but never used, so the GITHUB_WORKSPACE race above could not have been caught in CI. I've left the workflow alone here to keep this PR scoped. The suite now passes both sequentially and with the intended parallel config (73/73, repeated runs), so enabling it should be safe.

    Status: all 52 checks green on c8d1ade. Defect 2 (workflow_dispatch discarding the merged PR's label) is untouched and still needs its own issue.

    Arnaud CHARLES (@arnaudcharles) going to 1.4.0 is the right call — 1.3.1 was only ever an artifact of the cancelled push plus the patch-bump fallback.

  5. MariusStorhaug commented on Sep 2, 2026

    @MariusStorhaug
    Member

    Reproduced independently in MariusStorhaug/MariusTestModule

    Confirmed the defect on a second module, in my own account, on a caller that is already correctly configured for v8 — so this is a clean reproduction of Defect 1 with no interference from Defect 2.

    Failing run: MariusStorhaug/MariusTestModule run 33596331743 → Publish-Module job

    The failure is byte-for-byte the reported one, at the same line of the same file:

    Find-PSResource: /home/runner/work/MariusTestModule/MariusTestModule/_wf/.github/actions/Publish-PSModule/src/publish.ps1:148
    Line |
     148 |  … edPackage = Find-PSResource -Name $name -Version $publishPSVersion -R …
         |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         | Package with name 'MariusTestModule', version '0.4.13' could not be
         | found in repository 'PSGallery'.
    ##[error]Process completed with exit code 1.
    

    Why this reproduction is the cleaner signal

    Trigger push on main (merge of #62, 86edc38)
    Caller ref PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
    Caller concurrency cancel-in-progress: false, PR-or-ref group key
    Plan job success — resolved 0.4.13
    Publish-Module failure at the Gallery existence probe
    Publish-Site success

    Unlike the PSWEE case, this run was not cancelled and needed no manual workflow_dispatch recovery. The caller already carries the v8 concurrency block, and the resolved version was correct: Gallery latest and latest GitHub release were both 0.4.12, 0.4.13 was the right next patch, and PR #62 carried no bump label (so autopatching applied correctly).

    That isolates the cause. Nothing about version resolution was wrong here — the run failed purely because the existence probe made an absent version fatal. Every new version on any module hits this, which matches "any first release, and every subsequent new version".

    Blast radius confirmed

    0.4.13 reached neither destination:

    • PowerShell Gallery latest is still 0.4.12; 0.4.13 is absent.
    • No 0.4.13 GitHub release exists — Publish-Module fails before release creation.

    So the stage is fail-closed, not partially applied. Nothing needs cleanup before re-running, and no Gallery version was burned.

    Correction to an earlier draft of this note: the daily scheduled main runs in this repo have also been failing since at least 2026-08-26, but not for this reason. Those runs complete with zero jobs and no downloadable logs, so they fail at workflow startup before any action executes. Unrelated to this defect, and not investigated here.

  6. MariusStorhaug commented on Sep 2, 2026

    @MariusStorhaug
    Member

    Next: end-to-end validation against the branch

    The unit tests prove the probe logic in isolation, and #528 reproduces the defect end to end. What is still unproven is that the fix works in a real pipeline, so I am pointing MariusStorhaug/MariusTestModule at this PR's branch and driving a real publication through it.

    Approach

    MariusTestModule currently calls PSModule/Process-PSModule/.github/workflows/workflow.yml@v8. I am opening a PR there that switches the caller to @copilot/fix-publish-module-error and makes one trivial source change (a function description) so there is something to release.

    This works because Publish-Module.yml checks out its own actions at the resolved workflow commit rather than a floating tag:

    - name: Checkout Process-PSModule
      uses: actions/checkout@...
      with:
        repository: ${{ job.workflow_repository }}
        ref: ${{ job.workflow_sha }}
        path: _wf

    So changing the caller's uses: ref is sufficient — _wf/.github/actions/Publish-PSModule/src/publish.ps1 will be the fixed file from c8d1ade, with no vendoring or pinning workarounds needed.

    What the reproduction gives us for free

    The failed run left the Gallery at 0.4.12 with no 0.4.13 release, so the module is a clean starting point: 0.4.13 is still an unpublished version, and it is exactly the version that previously failed. That makes this a direct before/after on the same version rather than an approximation.

    Success criteria

    Stage Expected
    Plan resolves the next version
    Publish-Module (PR run) prereleases publish to the Gallery
    Publish-Module (merge to main) stable version publishes; no PackageNotFound failure
    GitHub release created for the new version

    The decisive signal is the merge-to-main publication: the branch's Find-PSResource probe must return "not found" without failing the job, then hand off to Publish-PSResource.

    I will also confirm the narrowed error filter did not break the resume path — re-running the publish once the version exists should skip the upload with the ♻️ Resuming Gallery-only publication notice rather than attempting a conflicting re-upload.

    Reporting results back here either way. If the fix does not hold end to end, that is the more useful finding.

  7. MariusStorhaug commented on Sep 2, 2026

    @MariusStorhaug
    Member

    End-to-end validation: the fix works

    Validated against a real publication in MariusStorhaug/MariusTestModule via MariusStorhaug/MariusTestModule#63, with the caller pointed at @copilot/fix-publish-module-error. Both branches of the probe are now confirmed on live infrastructure.

    Branch 1 — new version publishes (the reported defect)

    Run: 33597824748 → Publish-Module — success

    ##[group]Publish to PSGallery
    Publish module to PowerShell Gallery using API key from environment.
    MariusTestModule 0.4.13-validatepublishfixpr529001 is not on the PowerShell Gallery yet.
    ##[notice]https://www.powershellgallery.com/packages/MariusTestModule/0.4.13-validatepublishfixpr529001
    Gallery publishing complete. Version: [0.4.13-validatepublishfixpr529001]
    

    That "is not on the PowerShell Gallery yet" line is the new code path, at the same publish.ps1:148 probe that previously terminated the job. Same repository, same module, same 0.4.13 base version that failed in run 33596331743 — this is a direct before/after, not an approximation.

    Independently confirmed the package is live on the Gallery, so the success is a real upload rather than a silently skipped stage.

    Branch 2 — resume path intact (the review nit)

    The concern with narrowing the filter was breaking the idempotent resume the probe exists to provide. Re-ran the same publish job with the version now present:

    Re-run: job 100146802704 — success

    ##[notice]MariusTestModule 0.4.13-validatepublishfixpr529001 is already published to the PowerShell Gallery.
    Gallery publishing complete. Version: [0.4.13-validatepublishfixpr529001]
    

    No re-upload attempted, no 409. Release-PSModule also resumed correctly: GitHub release [v0.4.13-...] already exists. Resuming its artifact upload.

    Summary

    Condition Expected Result
    Version absent from Gallery log and publish ✅ published
    Version already on Gallery skip upload, continue ✅ skipped with ♻️ notice
    Non-PackageNotFound failure stay fatal ✅ covered by unit test
    Gallery state package live ✅ confirmed

    One thing worth noting

    My first validation run came back all-green with Publish-Module skipped — no publication, so the fix was untested despite the green checkmark. Cause: the PR carried no labels, and for an open PR ReleaseType resolves to None (AutoPatching applies to the merge, not the open PR). Adding the prerelease label produced the real publication.

    Flagging it because a green run here does not by itself mean the publish path executed. Anyone re-validating should confirm Publish-Module actually ran and check for the is not on the PowerShell Gallery yet line, rather than trusting the overall run status.

    Still outstanding

    • PR #63 is intentionally left as a draft and must not merge with the caller pinned to a branch. It reverts to @v8 once 🪲 [Fix]: New module versions publish to the PowerShell Gallery #529 ships in a tag.
    • Defect 2 (workflow_dispatch on the default branch discarding the merged PR's label) is unchanged and still needs its own issue. Related observation from this exercise: the workflow_dispatch I ran on the feature branch skipped publish entirely, which is correct — the defect is specific to dispatch on the default branch.
  8. MariusStorhaug commented on Sep 2, 2026

    @MariusStorhaug
    Member

    Confirmed fixed in production — v8.0.4

    v8 now points at 68696b6 (released as v8.0.4), and I verified the narrowed probe is present at the tag. MariusStorhaug/MariusTestModule was re-pointed to @v8 and driven through a real release.

    The stable release path now works

    MariusStorhaug/MariusTestModule#63 merged, triggering run 33609549477 → Publish-Module — success:

    GalleryVersion   : 0.4.13
    MariusTestModule 0.4.13 is not on the PowerShell Gallery yet.
    ##[notice]https://www.powershellgallery.com/packages/MariusTestModule/0.4.13
    Gallery publishing complete. Version: [0.4.13]
    

    0.4.13 is the exact version that failed in run 33596331743. Same module, same version, same probe — now publishes.

    Verified independently on both destinations:

    Destination Before After
    PowerShell Gallery latest 0.4.12 0.4.13
    GitHub release none for 0.4.13 v0.4.13

    Coverage across both paths and both branches

    Path Version Run Result
    Prerelease, branch build 0.4.13-…001 33597824748 ✅ published
    Resume (version present) 0.4.13-…001 re-run ✅ skipped upload
    Prerelease, released @v8 0.4.13-…002 33608326141 ✅ published
    Stable, released @v8 0.4.13 33609549477 ✅ published

    The stable path is the one that matters for consumers and was previously unproven — the earlier validation only exercised prereleases against a branch build. This closes that gap.

    The validation PR's temporary branch pin has been reverted to @v8 and the PR is merged, so the test module is back on a supported ref with no leftover pin.

    Two notes from the release

    • The post-merge Workflow-Test [Default] push run failed, but not from this change: Unable to download artifact(s): Failed to ListArtifacts: … (403) Forbidden. GitHub infrastructure, non-retryable. WithManifest passed and its Publish-Module succeeded.
    • The Release run for the merge sat at action_required with zero jobs until approved manually, which is why the tag lagged the merge by ~50 minutes. Every earlier Release run on 🪲 [Fix]: New module versions publish to the PowerShell Gallery #529 ran without approval, so this looks specific to the pull_request: closed event on a PR authored by app/copilot-swe-agent. Worth watching on the next merge — if it recurs, releases are effectively gated on a human click and that deserves its own issue.

    Still open

    Arnaud CHARLES (@arnaudcharles) the fix is live in v8.0.4, so PSWEE 1.4.0 should publish now. Two things on your side:

    1. Your caller still needs the v8 concurrency block (cancel-in-progress: false, PR-or-ref group key, and the unlabeled PR type). The cancelled merge push is what forced the manual recovery that produced the wrong version — see Calling the workflow.
    2. Until that is in place, avoid workflow_dispatch on the default branch as a recovery route: Defect 2 below means it silently discards the merged PR's version label.

    Defect 2 remains unfixed and untracked — workflow_dispatch on the default branch resolves no associated pull request (PR association is gated on $isPush in Get-PSModuleSettings/src/main.ps1), so a manual recovery run discards the merged PR's version label and silently falls back to Patch. Higher blast radius than the bug just fixed, because Gallery versions cannot be reclaimed once taken. This issue can close on the fix that shipped, but Defect 2 needs its own issue before the analysis here is lost.

  9. arnaudcharles commented on Sep 3, 2026

    @arnaudcharles
    Author

    Moved back to v8 and working like expected ! Thanks Marius Storhaug (@MariusStorhaug)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions