Skip to content

fix(updater): handle 4-part version strings to prevent TypeInitializationException on nightly builds#2249

Merged
Belphemur merged 3 commits into
devfrom
copilot/soundswitch-41w-fix-type-initialization-exception
Jun 23, 2026
Merged

fix(updater): handle 4-part version strings to prevent TypeInitializationException on nightly builds#2249
Belphemur merged 3 commits into
devfrom
copilot/soundswitch-41w-fix-type-initialization-exception

Conversation

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Nightly builds stamp AssemblyInfo with a 4-part version like 7.1.0.229925. SemanticVersion.Parse (NuGet.Versioning) rejects this format, causing the static initializer of UpdateChecker to throw — poisoning the entire type and breaking all update checks.

Changes

  • UpdateChecker.cs — Added ParseVersion() helper that maps a 4-part version to a valid semver by using the last 5 digits of the revision component as the patch number (e.g. 7.1.0.2299257.1.29925). This preserves ordering between nightly builds rather than collapsing them all to the same version:

    private static SemanticVersion ParseVersion(string version)
    {
        var parts = version.Split('.');
        if (parts.Length >= 4 && int.TryParse(parts[3], out var revision))
        {
            var patch = revision % 100_000;
            return new SemanticVersion(int.Parse(parts[0]), int.Parse(parts[1]), patch);
        }
        var truncated = string.Join(".", parts.Take(3));
        return SemanticVersion.Parse(truncated);
    }

    Used for the static AppVersion field (the crash site) and applied a defensive .Where(TryParse) filter in CheckForUpdate to skip any release tags that aren't valid semver.

  • VersionTest.cs — Added parameterized tests covering 4-part nightly versions and standard 3-part versions to guard against regression.

Nightly releases stamp AssemblyInfo with a version like "7.1.0.229925"
which has a fourth component not accepted by NuGet.Versioning's
SemanticVersion.Parse, causing a TypeInitializationException.

- Add ParseVersion() helper that truncates to major.minor.patch before
  parsing, making static AppVersion initialization safe for nightly builds
- Add a Where filter in CheckForUpdate to skip releases whose tag cannot
  be parsed as a SemanticVersion (defensive guard)
- Add VersionTest cases for 4-part nightly version truncation

Closes #2248

Co-authored-by: Belphemur <197810+Belphemur@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix System.TypeInitializationException for UpdateChecker fix(updater): handle 4-part version strings to prevent TypeInitializationException on nightly builds Jun 23, 2026
Copilot AI requested a review from Belphemur June 23, 2026 16:49
@Belphemur

Copy link
Copy Markdown
Owner

@copilot Only keep the last 4 or 5 digits of the patch when present, if that is acceptable for version parsing

Instead of discarding the 4th version component entirely (which made all
nightly builds report as the same version), use the last 5 digits of the
revision number as the patch component.

e.g. "7.1.0.229925" → SemanticVersion 7.1.29925

This preserves ordering between nightly builds and is acceptable to
NuGet.Versioning's SemanticVersion parser.

Co-authored-by: Belphemur <197810+Belphemur@users.noreply.github.com>

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Only keep the last 4 or 5 digits of the patch when present, if that is acceptable for version parsing

Done. ParseVersion now uses revision % 100_000 (last 5 digits) of the 4th component as the semver patch, so 7.1.0.2299257.1.29925. This keeps nightly builds orderable while staying within valid semver range. Committed in the latest push.

@Belphemur Belphemur marked this pull request as ready for review June 23, 2026 21:12
Copilot AI review requested due to automatic review settings June 23, 2026 21:12
@Belphemur Belphemur merged commit 7149cd5 into dev Jun 23, 2026
12 checks passed
@Belphemur Belphemur deleted the copilot/soundswitch-41w-fix-type-initialization-exception branch June 23, 2026 21:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the updater’s version-handling logic so nightly builds stamped with a 4-part version string (e.g., 7.1.0.229925) no longer crash UpdateChecker during static initialization, restoring update-check functionality for nightlies.

Changes:

  • Added ParseVersion() helper and used it for UpdateChecker’s static AppVersion initialization.
  • Added filtering of GitHub releases to skip tags that can’t be parsed as semantic versions before ordering.
  • Added/updated tests for nightly (4-part) and standard (3-part) version parsing, plus some formatting-only changes in icon extractor files.

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated 4 comments.

File Description
SoundSwitch/Framework/Updater/UpdateChecker.cs Introduces ParseVersion() for 4-part versions and filters invalid release tags before ordering.
SoundSwitch.Tests/VersionTest.cs Adds parameterized tests for nightly/standard version parsing behavior.
SoundSwitch.Tests/AudioDeviceIconExtractorTests.cs Formatting/whitespace-only normalization.
SoundSwitch.Common/Framework/Audio/Icon/AudioDeviceIconExtractor.cs Formatting/whitespace-only normalization.

Comment on lines +131 to +133
foreach (var release in (releases ?? Array.Empty<Release>())
.Where(release => SemanticVersion.TryParse(release.TagName.Substring(1), out _))
.OrderByDescending(release => SemanticVersion.Parse(release.TagName.Substring(1))))
Comment on lines +27 to +38
var parts = rawVersion.Split('.');
SemanticVersion parsed;
if (parts.Length >= 4 && int.TryParse(parts[3], out var revision))
{
var patch = revision % 100_000;
parsed = new SemanticVersion(int.Parse(parts[0]), int.Parse(parts[1]), patch);
}
else
{
var truncated = string.Join(".", parts.Take(3));
parsed = SemanticVersion.Parse(truncated);
}
Comment on lines +52 to +61
var parts = version.Split('.');
if (parts.Length >= 4 && int.TryParse(parts[3], out var revision))
{
var patch = revision % 100_000;
return new SemanticVersion(int.Parse(parts[0]), int.Parse(parts[1]), patch);
}

var truncated = string.Join(".", parts.Take(3));
return SemanticVersion.Parse(truncated);
}
Comment on lines +21 to +24
[TestCase("7.1.0.229925", "7.1.29925")]
[TestCase("1.2.3.456", "1.2.456")]
[TestCase("1.2.3.100001", "1.2.1")]
[TestCase("1.2.3", "1.2.3")]
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.

System.TypeInitializationException: The type initializer for 'SoundSwitch.Framework.Updater.UpdateChecker' threw an exception.

3 participants