Skip to content

Fix ExtractFormattedVersionTag to preserve non-numeric channel suffixes (e.g. experimentalA) - #6656

Merged
agniuks merged 1 commit into
release/2.0-experimentalfrom
agnel/fix-extractformattedversiontag-letter-suffix
Jul 28, 2026
Merged

Fix ExtractFormattedVersionTag to preserve non-numeric channel suffixes (e.g. experimentalA)#6656
agniuks merged 1 commit into
release/2.0-experimentalfrom
agnel/fix-extractformattedversiontag-letter-suffix

Conversation

@agniuks

@agniuks agniuks commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Root cause

DeploymentManager::GetStatus derives the short channel tag for the Main and Singleton package family names via ExtractFormattedVersionTag. The old implementation only appended a numeric suffix (swscanf %u), so a trailing letter suffix such as the "A" in experimentalA was dropped, yielding -e instead of -eA.

As a result GetStatus built ...WinAppRuntime.Main.2-e_... while the installed package is ...WinAppRuntime.Main.2-eA_.... The runtime was reported as not installed, triggering a spurious deploy/repair that fails with access denied on Server 2019 / RS5. This surfaced as ~40s AppLaunchWaiter timeouts in the five *CppWinuiPackaged sample-launch tests on the two Windows 10 17763 (RS5) legs of the 2.0-experimental release build.

Fix

Rework ExtractFormattedVersionTag to mirror the build-time short-tag derivation in CreateBuildInfo.ps1 (regex ^([a-z.]+)([A-Z0-9]{0,2})$), appending the trailing [A-Z0-9]{0,2} suffix so runtime and build agree for both numeric and letter suffixes.

Verification

Compared old vs new vs build derivation over a tag table:

tag old new build
experimental -e -e -e
experimental10 -e10 -e10 -e10
experimentalA -e -eA -eA
experimental2 -e2 -e2 -e2
preview3 -p3 -p3 -p3
stable -s -s -s

Behavior changes only for the previously-broken letter-suffix tags; all numeric/plain tags are unchanged (backward compatible).

AB#62727253

DeploymentManager::GetStatus derives the short channel tag for the Main and
Singleton package family names via ExtractFormattedVersionTag. The old code
only appended a numeric suffix (swscanf %u), so a trailing letter suffix such
as the "A" in "experimentalA" was dropped, yielding "-e" instead of "-eA".
GetStatus then built a package family name that never matched the installed
Main/Singleton packages, reported the runtime as not installed, and forced a
spurious deployment that fails with access denied on Server 2019 / RS5.

Mirror the build-time short-tag derivation in CreateBuildInfo.ps1
(regex ^([a-z.]+)([A-Z0-9]{0,2})$) so runtime and build agree for numeric and
letter suffixes. Verified: identical output for plain/numeric tags
(experimental -> -e, experimental10 -> -e10) and corrected for letter suffixes
(experimentalA -> -eA).

AB#62727253
@agniuks
agniuks requested a review from a team July 28, 2026 22:33
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@agniuks

agniuks commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.

@JesseCol

Copy link
Copy Markdown
Contributor

This content was largely generated by AI. AI makes mistakes.

I investigated the underlying failure (AB#63309642) and independently arrived at the same root cause, so I went through this closely. The fix is correct — I reimplemented old, new, CreateBuildInfo.ps1, and the existing runtime helper and compared them across a tag table:

tag OLD PR #6656 BUILD
experimental9 -e9 -e9 -e9 agree
experimentalA -e -eA -eA fixed
experimentalZ -e -eZ -eZ fixed
experimental1M -e1 -e1M -e1M fixed
experimental10 -e10 -e10 -e10 agree
preview3 / dev.channel / stable same same same agree

Backward compatible as claimed. The regex correctly expects the leading hyphen (^-...), matching how GetStatus passes packageNameVersionTag. Static local wregex is thread-safe. No correctness objection.

That said, I have one substantive suggestion and a few smaller items.


Main suggestion: this logic already exists, correctly, in the codebase

dev/Common/AppModel.Identity.h already has:

/// VersionShortTag for v2.x+ = VersionTag[0] + ChannelBuildString
/// The Tag portion uses only lowercase letters and dots [a-z.], while the
/// ChannelBuildString (base-36 encoded) uses only digits and uppercase [0-9A-Z].
/// These disjoint character sets allow deterministic extraction.
inline std::wstring GetVersionShortTagFromVersionTagV2(PCWSTR versionTag)

I verified it produces identical output to this PR on every valid tag. IsValidVersionShortTag just below it even documents v2.x format: letter + base36(revision) e.g. "c0", "c1M", "pZ".

So the base36 channel revision scheme was already known, already implemented correctly, and already shipped for the Bootstrap/DDLM path with an explicit V1/V2 split on majorMinorVersion >= 0x00020000. MddBootstrap.cpp calls ...V2(). DeploymentManager.cpp just never got migrated and kept its own private copy.

This PR adds a third in-tree implementation of the same rule (plus the PowerShell in CreateBuildInfo.ps1 and the C# sample in the spec — five copies total). That duplication is the reason this bug existed.

Suggested instead: call AppModel::Identity::GetVersionShortTagFromVersionTagV2(). That namespace is already in scope in this file (IsPackagedProcess(), PackageIdentity::FromPackageFullName). The only wrinkle is that it takes the tag without the leading -, so the caller needs a small adjustment.

Three concrete benefits beyond dedup:

1. Avoids std::regex on the app startup path. This runs GetStatusInitialize → the #pragma init_seg(lib) static ctor in WindowsAppRuntimeAutoInitializer.cpp, i.e. every packaged WinAppSDK app, at every launch, before wWinMain. std::regex is notoriously heavy for binary size and first-use construction cost. GetVersionShortTagFromVersionTagV2 is a simple backward scan with no allocation beyond the result.

2. Validation instead of silent failure. If the regex doesn't match, this PR still returns "-" + versionTag[1] — a partial, wrong short tag, silently. That is exactly the failure mode being fixed here: a wrong family name → package "not found" → spurious deploy → access denied on RS5 → app exits 0x80070005 with no window and no error. GetVersionShortTagFromVersionTagV2 routes through IsValidVersionShortTag and throws E_INVALIDARG with the offending tag. Given how expensive the silent version was to diagnose, failing loudly seems clearly better.

3. Guaranteed agreement with the DDLM/Bootstrap path. Two independent implementations of one naming rule can drift apart again. One can't.


Branch coverage — worth confirming before merge

ExtractFormattedVersionTag still has the old swscanf_s on main and release/2.0-stable as well as this branch.

There's a specific hazard: I see recurring "Snap release/2.0-stable to release/2.0-experimental" PRs (#6634, #6527). A future snap could revert this fix if stable isn't also fixed. release/2.0-stable has no version tag so it shows no symptom today, but it needs the code change for that reason alone. main needs it outright — that's AB#62727253, which is the same bug.

Tests

Nothing in the repo currently references ExtractFormattedVersionTag, GetVersionShortTagFromVersionTagV2, or IsValidVersionShortTag from a test.

The table in your PR description is a test table — test/Deployment/API/APITests.cpp looks like the natural home. Worth the small cost: this bug sat latent for 9 channels, and there was a prior instance of the same RS5 failure 15 months ago (AB#57337552). A unit test asserting runtime short-tag == build short-tag over a tag table would catch the next divergence at PR time instead of in an RC.

Spec doc still documents the bug

specs/Deployment/MSIXPackages.md is unchanged and still wrong in two places:

  • Lines 178 and 205: "ShortVersionTag is derived from a VersionTag by combining the 1st letter and the last digit (if any)"
  • Lines 207–226: a ToShortVersionTag C# reference implementation with the identical digits-only defect (if (('0' <= lastChar) && (lastChar <= '9')))

If the code is fixed and the spec isn't, the next person implementing against the spec reproduces the bug. Worth updating in this PR.

Nit: work item reference

AB#62727253 tracks the main instance. This build failure is tracked by AB#63309642. Probably worth referencing both, since this PR targets the branch that AB#63309642 is filed against.


Verdict

Approve on correctness — this does fix the bug, and the verification table in the description is exactly the right way to demonstrate it.

I'd advocate for switching to GetVersionShortTagFromVersionTagV2() before merge: smaller diff, drops the <regex> dependency from a hot startup path, adds validation, and removes a duplicate implementation rather than adding one.

If schedule pressure means shipping as-is, that's a reasonable call — but the branch coverage point is the one I'd not defer, because a snap from release/2.0-stable could silently undo this.

@agniuks
agniuks merged commit 15389ce into release/2.0-experimental Jul 28, 2026
11 of 20 checks passed
@agniuks
agniuks deleted the agnel/fix-extractformattedversiontag-letter-suffix branch July 28, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants