Repository navigation
Restore the configured VersionPrefix on release tags created by Publish-PSModule #439
Description
Context and request
Observed behavior: since v6, Publish-PSModule derives the git tag from the compiled manifest's ModuleVersion instead of from the resolved FullVersion. ModuleVersion is Major.Minor.Patch by definition — the script even enforces ^\d+\.\d+\.\d+$ — so the configured VersionPrefix is dropped and the release is tagged without it.
.github/actions/Publish-PSModule/src/publish.ps1 (v6.1.15):
$moduleVersion = $manifestData.ModuleVersion
if (-not ($moduleVersion -match '^\d+\.\d+\.\d+$')) { ... }
...
$releaseTag = if ($createPrerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
...
$releaseCreateCommand = @('release', 'create', $releaseTag)The Plan job resolves the prefix correctly and passes it through. From the PSSemVer publish run for PR PSModule/PSSemVer#46:
VersionPrefix : v
GitHub version: [v1.1.9]
New version: [v1.1.10]
"Resolution":{"Version":"1.1.10","Prerelease":"","FullVersion":"v1.1.10","ReleaseType":"Release","CreateRelease":true}
FullVersion is v1.1.10; the tag created is 1.1.10.
Expected behavior: the release tag is the resolved FullVersion, so VersionPrefix: 'v' keeps producing v1.1.10 and a repository's tag history stays consistent.
Reproduction: merge any release-labelled pull request in a module repository pinned to v6 whose settings leave VersionPrefix at the default v, then compare the new tag to the previous one.
Environment: Process-PSModule v6.0.0 through v6.1.15; any module repository using the default VersionPrefix: 'v'.
Regression: yes. v5 produced prefixed tags. Three repositories have already published on v6 and every one of them lost the prefix at the v6 boundary:
| Repository | Last v5 tag | First v6 tag |
|---|---|---|
PSModule/Toml |
v0.0.1 |
0.0.2 |
PSModule/Domeneshop |
v0.0.2 |
1.0.0, 1.0.1 |
PSModule/PSSemVer |
v1.1.9 |
1.1.10 |
This looks like a side effect of #326, which made publish read the version from the manifest read-only. The manifest is the right source for the module version, but it cannot carry the tag prefix.
Workaround: none that preserves history. Setting VersionPrefix: '' makes the repository consistent going forward but abandons every existing v-prefixed tag.
Acceptance criteria:
- With
VersionPrefix: 'v', a stable release is taggedv<Major>.<Minor>.<Patch>and a prerelease is taggedv<Major>.<Minor>.<Patch>-<label>. - With
VersionPrefix: '', tags stay unprefixed. AutoCleanupstill matches and removes the prerelease tags it created, in both prefixed and unprefixed repositories.- A regression test covers a prefixed repository, so the prefix cannot be dropped silently again.
Technical decisions
The root cause is bounded to the tag derivation in .github/actions/Publish-PSModule/src/publish.ps1. The manifest check should stay — it is a valid guard that the artifact was stamped — but $releaseTag should come from the Plan job's FullVersion output (or from Version plus the settings VersionPrefix), not from $moduleVersion. The ModuleVersion validation and the 999.0.0 placeholder check remain unchanged.
Two decisions are needed:
- Whether the already-published unprefixed tags are corrected. Retagging
PSSemVer 1.1.10,Toml 0.0.2, andDomeneshop 1.0.0/1.0.1would restore consistency, but the releases and their attached artifacts are already public, and1.1.10is the tag the PowerShell Gallery listing links to. Leaving them and fixing forward is the lower-risk option; either way the choice should be explicit. - Whether prefix consistency is enforced.
Resolve-PSModuleVersion'sGet-LatestGitHubVersionreadstagNameandGet-LatestPublishedVersionsorts a prefixed and an unprefixed[PSSemVer]together —CompareToignoresPrefix, so equal versions sort arbitrarily and the prefix can be lost from the resolved latest version. That is survivable because the prefix is reapplied from settings afterwards, but a repository with mixed tags is worth a warning.
Blast radius grows with #438: 48 of 58 module repositories are still on v5 and will hit this the first time they publish after migrating.
Implementation plan
Add a regression test that publishes from a fixture repository configured with VersionPrefix: 'v' and asserts the created tag, confirm it fails against current main, change $releaseTag to use the resolved full version, re-run the prefixed and unprefixed fixtures plus the AutoCleanup path, then record the decision on the already-published tags.
Activity
MariusStorhaug commentedon Aug 2, 2026
Implementation plan progress — all steps except the recorded decision are done in #440.
Regression test added, confirmed failing against the current derivation.
.github/actions/Publish-PSModule/tests/Publish-PSModule.Helpers.Tests.ps1covers a prefixed repository (stable and prerelease), an unprefixed repository, prefix/label normalization, and the tag-shape contractCleanup-PSModulePrereleasesdepends on. TheTest-Actionsworkflow ran red on the test-only commit and green on the fix commit, on this branch:Test actionsfd2c7d9d0f8f7a06b4714$releaseTagnow carries the prefix. The manifest stays the source of the numeric version — the^\d+\.\d+\.\d+$guard and the999.0.0placeholder check are untouched — and only the prefix, the one thingModuleVersioncannot carry, comes fromSettings.Publish.Module.VersionPrefix.Publish-Module.ymlpasses it into a newVersionPrefixinput on thePublish-PSModuleaction.Prefixed and unprefixed paths re-run.
publish.ps1was executed end to end inWhatIfmode against a fabricated artifact for all four combinations. ThePSSemVercase now producesgh release create v1.1.10and exportsPSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag=v1.1.10; withVersionPrefix: ''it still produces1.1.10. The PowerShell Gallery version and the attached zip name stay unprefixed.AutoCleanupre-verified.cleanup.ps1was executed against fixture release lists with theghCLI shadowed. It selects and excludes correctly for a prefixed repository, an unprefixed repository, and a repository whose history still holds the unprefixed prerelease tags v6 created before this fix — the*$prereleaseName*match is prefix-agnostic and the exclusion compares the exported tag totagName, and both sides are now prefixed together.Decision 1 — already-published unprefixed tags
Recommendation: leave them and fix forward.
PSSemVer 1.1.10,Toml 0.0.2, andDomeneshop 1.0.0/1.0.1are public releases with attached artifacts, and the PowerShell Gallery listing links to1.1.10. Retagging breaks those links and the artifact URLs for no functional gain; the only cost of leaving them is one cosmetic gap in each repository's tag history. Nothing in the pipeline reads tags by prefix —Get-LatestGitHubVersionreadstagNameand parses it as a version — so an unprefixed tag in the history does not affect future version resolution.Decision 2 — prefix consistency enforcement
Out of scope for this bounded fix; filed separately so this pull request stays reviewable.