Skip to content

An app published trimmed or Native AOT crashes on its first ShouldLaunch() with "Reflection-based serialization has been disabled" #210

Open

Description

@matt-edmondson

What's wrong

SingleAppInstance reads and writes its PID file with reflection-based System.Text.Json:

  • SingleAppInstance/SingleAppInstance.cs:217: JsonSerializer.Deserialize<ProcessInfo>(ref reader)
  • SingleAppInstance/SingleAppInstance.cs:472: JsonSerializer.Serialize(processInfo)

An app published with PublishTrimmed=true or PublishAot=true gets System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault=false by default, which is visible in the published runtimeconfig. Under that setting, both calls throw InvalidOperationException. Neither call site catches it:

  • TryWritePidFile catches only IOException and UnauthorizedAccessException.
  • The read path catches only JsonException.

The exception therefore escapes ShouldLaunch() and ExitIfAlreadyRunning(), and the app dies at startup:

Unhandled exception. System.InvalidOperationException: Reflection-based serialization has been disabled for this application ...
   at ktsu.SingleAppInstance.SingleAppInstance.WritePidFile() line 472

How to reproduce

  1. Create a console app that references the library and calls SingleAppInstance.ShouldLaunch().
  2. Either publish it with dotnet publish -r linux-x64 --self-contained -p:PublishTrimmed=true, or keep the normal JIT build and set System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault to false in its runtimeconfig.
  3. Run it.

The JIT variant reproduces the crash directly. The publish itself only reports IL2026 warnings for lines 217 and 472, which are easy to miss.

Note on trimmed publishes: a trimmed publish currently hits an earlier failure before it reaches the JSON calls. ReadPidFileState throws TypeLoadException: Method 'CompareTo' in type 'ktsu.Semantics.Paths.AbsoluteFilePath' ... does not have an implementation, which is a trimming problem in ktsu.Semantics.Paths. Rooting the Semantics assemblies gets past it, and the app then fails with the JSON exception above. Fixing this repository's JSON usage is needed either way.

Why it matters

Single-instance enforcement is mostly used by desktop apps, tray apps and CLI tools. Those are the apps most likely to be published trimmed, single-file or AOT. For them, the library turns "only one instance" into "no instance": the app crashes on every launch.

Suggested fix

  • Add a source-generated context, for example [JsonSerializable(typeof(ProcessInfo))] internal sealed partial class ProcessInfoJsonContext : JsonSerializerContext;, and pass ProcessInfoJsonContext.Default.ProcessInfo to both calls.
  • Set <IsAotCompatible>true</IsAotCompatible> (or IsTrimmable) on the library project for the TFMs that support it, so the trim and AOT analyzers flag regressions at build time.

Acceptance criteria

  • With IsReflectionEnabledByDefault=false, ShouldLaunch() writes the PID file and returns true for the first instance, and false for a second instance while the first is running.
  • Building the library produces no IL2026/IL3050 warnings.
  • Existing PID files written by the current version still read correctly. The JSON shape is unchanged.

Activity

  1. matt-edmondson commented on Nov 4, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    • Category: Bug
    • Priority: High. Any consumer that publishes trimmed or Native AOT crashes on startup at the first ShouldLaunch(), with no workaround short of turning reflection back on.
    • Area: PID-file serialization (SingleAppInstance.cs read at :217, write at :472)
    • Duplicates: none found in the org.
    • In progress: not covered. Open PR Throw for a PID file that can't be read or written instead of reporting another instance [patch] #209 changes the IO/access exception handling around the same call sites, so either rebase on it or land this after it.
    • Suggested fix: add a source-generated JsonSerializerContext for ProcessInfo and pass its JsonTypeInfo to both calls. A trimmed or AOT smoke test in CI would stop this coming back.

    Generated by Claude Code

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions