Skip to content

fix(sentry): report the real app version as the release, on every head - #149

Merged
tylerkron merged 1 commit into
mainfrom
fix/126-sentry-release-version
Sep 1, 2026
Merged

tylerkron merged 1 commit into
mainfrom
fix/126-sentry-release-version

Conversation

@tylerkron

@tylerkron tylerkron commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Not merging — for review.

Closes #126.

What was wrong

Every crash and error the Android app sent to Sentry arrived tagged release: 0.0.0. That is not a version this app has ever had, so release health, regression detection and "which build introduced this?" were all inert for mobile: crashes from every build in the wild landed in one undifferentiated bucket. (dist still carried the Android versionCode, so triage was degraded rather than blind — which is why this wasn't a ship blocker.)

The release string came from a line inherited from the Windows app:

var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0";

That is sound upstream, where there is exactly one head and it is always the entry assembly. It asks a question whose answer is a property of the head, not of the app — and this port has three heads:

head GetEntryAssembly()?.GetName().Version reported release
Android no managed entry assembly (the process starts through JNI, not a managed Main), so GetEntryAssembly() is null 0.0.0 — the literal fallback
iOS the head; no <Version> declared, but the Apple SDK derives it from <ApplicationDisplayVersion> 3.3.0.0
Desktop the head, which declares <Version> 3.3.0.0

So Android was broken and the other two were merely lucky — right number, wrong shape (the stores list 3.3.0, not 3.3.0.0), no commit attached, and resting on which assembly happens to be the entry point.

I checked the iOS head rather than assuming, and the issue's guess that it would report 1.0.0.0 turns out to be wrong: dotnet msbuild Daqifi.Avalonia.iOS.csproj -getProperty:Version evaluates to 3.3.0 even though the csproj sets no <Version>, because the Apple SDK maps ApplicationDisplayVersion onto it.

The fix

Ask the shared assembly — the one every head links in — instead of asking whichever assembly started the process.

New Daqifi.Desktop.Common.AppVersion reads AssemblyInformationalVersionAttribute, which is the same attribute the mobile shell's version label already used. The Sentry init and the on-screen version now come from one resolver, so what a user reads off the screen and what a crash is tagged with cannot disagree.

The release keeps the +<sha> build metadata (3.3.0+d7cfaff…) rather than being trimmed to 3.3.0. This is the deliberate choice the issue asked for: it makes every build its own release and maps a crash to an exact commit, which is what you want on mobile where a store version and a build are not 1:1. The user-facing label stays v3.3.0 — the SHA is dev noise on screen (#13).

Nothing invents a version any more. When the version cannot be resolved, options.Release is left unset so the Sentry SDK applies its own detection. A stand-in like "0.0.0" is indistinguishable in Sentry from a release that genuinely is 0.0.0, which is exactly how this stayed invisible until someone read a crash report closely.

Also: one place to bump the version

3.3.0 was hand-written in four projects — the shared library, the Desktop head, and the Android and iOS store display versions. Drift between the assembly Sentry reports and the version a store lists is the same class of bug as the one above, with the same symptom: a triage conversation starting from a false premise. All four now read DaqifiAppVersion from Directory.Build.props; bumping that one property cuts a release.

The per-store build numbers (ApplicationVersion — Android versionCode, iOS CFBundleVersion) are deliberately not folded in. They are per-store counters on their own schedule, and Play requires a unique increasing code for every upload including re-uploads of an unchanged marketing version.

Verification

There were no guarding tests to strengthen first — this repo has no test project at all. That is the honest finding, and it is why the verification below is a build-artifact read-back rather than a test.

The check was a throwaway console that loads the built Daqifi.Avalonia.dll and asks AppVersion for the strings the Sentry init consumes (reflection only, so nothing needs Avalonia loaded). It lived outside the repo and is not part of this change — the diff adds no test scaffolding.

Against the trimmed iOS Release assembly as it sits inside the built .app — the toughest case, since the iOS Release build trims and AOT-compiles, and a stripped attribute would silently reintroduce the bug:

assembly            : …/Daqifi.Avalonia.iOS.app/Daqifi.Avalonia.dll
Informational       : 3.3.0+d7cfafffdf711ccb4cce42600e7b7e3ab1e44fcc   <- Sentry options.Release
Semantic            : 3.3.0
ShortBuildMetadata  : d7cfaff
GetName().Version   : 3.3.0.0

d7cfaff… is this branch's head commit exactly. The same read-back against the desktop build output gives the same answer. The attribute survives the trimmer, and the release string is now identical on both heads I can build.

Also confirmed the MSBuild single-sourcing actually reached the heads: the built iOS bundle's Info.plist carries CFBundleShortVersionString = 3.3.0 (from $(DaqifiAppVersion)) with CFBundleVersion = 1 unchanged.

Build gate, all green, clean rebuilds:

result
Desktop succeeded, 297 warnings — equal to baseline
iOS restore succeeded (no -r; git status -- '**/packages.lock.json' empty, no lock churn)
iOS Release iossimulator-arm64 succeeded, 1253 warnings

On the iOS number: I measured origin/main in the same worktree under the identical clean-rebuild conditions and it is also 1253, so this change adds zero warnings. The 1249 figure in the loop brief appears to be a slightly different measurement state; the like-for-like comparison is the meaningful one, and 1233 is what an incremental build reports because the vendored OxyPlot's 20 warnings are not re-emitted.

What I did not verify

  • The Android head does not build on this Mac (workload absent), and Android is the head where the bug was actually observed. Its only change here is <ApplicationDisplayVersion>3.3.0</ApplicationDisplayVersion> → $(DaqifiAppVersion), which MSBuild evaluation confirms resolves to the same 3.3.0. I did not produce an Android build myself — CI did, and the Android head job passes on this branch (5m11s), along with every other check. What CI proves is that it compiles and packages; it does not exercise the release string at runtime.
  • No event was captured from a real device. The issue's acceptance check is a live event showing the new release tag; what I can show is the string the SDK will be handed, read out of the shipped assembly. Closing the loop needs a device run.
  • Not device-observable, so no bench validation and the board lock was not taken.

Left alone deliberately

MainWindow.axaml.cs also resolves a version, via Assembly.GetExecutingAssembly().GetName().Version. GetExecutingAssembly() there is the shared assembly, so it was never affected by this bug and already shows DAQiFi v3.3.0. It is an upstream-paired file carrying @port markers, and churning it for cosmetics would create a divergence to maintain for no behavioural gain.

The divergence from upstream's line, and the new downstream-only type, are recorded in .portomatic/map/Common.yaml (DIV-COM-001).

🤖 Generated with Claude Code

Sentry tagged every Android event `release: 0.0.0`, so release health,
regression detection and "which build introduced this?" were inert for mobile.

The release came from
`Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0"`,
inherited from upstream where it is sound: a WPF app has one head and it is
always the entry assembly. It asks a question whose answer is a property of the
head, not of the app, and this port has three heads. On Android the process
starts through JNI with no managed `Main`, so there is no entry assembly at all
and the call fell through to the literal fallback. iOS and desktop each did
answer — both with `3.3.0.0`, the right number in the wrong shape (the stores
list `3.3.0`) and with no commit attached.

Resolve the version from the shared assembly instead — the one every head links
in — through a new `Daqifi.Desktop.Common.AppVersion`. It reads
`AssemblyInformationalVersionAttribute`, the same attribute the mobile shell's
version label already used, so what a user reads off the screen and what a
crash is tagged with cannot disagree. The release keeps the `+<sha>` build
metadata: it makes each build its own release and maps a crash to an exact
commit, which matters on mobile where a store version and a build are not 1:1.

Nothing invents a version any more. When it cannot be resolved, `options.Release`
is left unset so the SDK applies its own detection; a placeholder like "0.0.0"
is indistinguishable in Sentry from a release that genuinely is 0.0.0, which is
how this stayed invisible until someone read a crash report closely.

Also single-source the version number itself. "3.3.0" was spelled out in four
projects — the shared library, the Desktop head, and both store display
versions — and drift between the assembly Sentry reports and the version a
store lists is the same class of bug. They now all read `DaqifiAppVersion` from
Directory.Build.props. The per-store build numbers stay per-store.

Closes #126
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Report accurate Sentry releases across all app heads

🐞 Bug fix ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Resolves release identity from the shared assembly across Android, iOS, and desktop.
• Reports informational versions with commit metadata to Sentry, avoiding false placeholders.
• Centralizes marketing versions and reuses the resolver for user-facing labels.
Diagram

graph TD
  V["Version Property"] --> H["Platform Heads"]
  V --> S["Shared Assembly"] --> R["AppVersion Resolver"]
  R --> U["Version Label"]
  R --> L["AppLogger"] --> E["Sentry Release"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Platform-native version APIs
  • ➕ Reads each installed package's store-visible version directly.
  • ➕ Avoids reflection over managed assembly attributes.
  • ➖ Requires separate Android, iOS, and desktop implementations.
  • ➖ Does not naturally include commit metadata for per-build Sentry releases.
  • ➖ Can diverge from the shared UI and telemetry version contract.
2. Generated version constants
  • ➕ Provides compile-time values without runtime reflection.
  • ➕ Can expose semantic and informational forms explicitly.
  • ➖ Adds source-generation or build-target plumbing.
  • ➖ Must carefully preserve SourceRevisionId metadata.
  • ➖ Introduces another generated artifact to keep aligned with package properties.

Recommendation: Keep the PR's shared-assembly resolver and centralized MSBuild property. It provides one cross-platform source, preserves commit metadata for Sentry, and lets UI and telemetry share normalization logic; platform APIs or generated constants add complexity without improving the current requirements.

Files changed (9) +221 / -20

Bug fix (2) +133 / -3
AppVersion.csAdd a cross-head application version resolver +120/-0

Add a cross-head application version resolver

• Reads informational version metadata from the shared assembly and exposes full, semantic, and short-build forms. Returns null instead of plausible placeholder versions when metadata is unavailable.

Daqifi.Avalonia/Daqifi.Desktop.Common/AppVersion.cs

AppLogger.csSend the real informational version to Sentry +13/-3

Send the real informational version to Sentry

• Replaces entry-assembly lookup with AppVersion.Informational, fixing Android's 0.0.0 release. Leaves Sentry release unset when resolution fails instead of reporting a false placeholder.

Daqifi.Avalonia/Daqifi.Desktop.Common/Loggers/AppLogger.cs

Refactor (1) +7 / -10
MobileShellView.axaml.csReuse AppVersion for the mobile version label +7/-10

Reuse AppVersion for the mobile version label

• Removes duplicate reflection and metadata parsing from the mobile shell. Keeps semantic versions user-facing while retaining short commit metadata in debug builds.

Daqifi.Avalonia/Views/MobileShellView.axaml.cs

Documentation (1) +40 / -3
Common.yamlRecord the downstream Sentry versioning divergence +40/-3

Record the downstream Sentry versioning divergence

• Documents why the multi-head port cannot use upstream entry-assembly version resolution. Registers AppVersion as a downstream-only class and file.

.portomatic/map/Common.yaml

Other (5) +41 / -4
Daqifi.Avalonia.Android.csprojSource Android versionName from the shared app version +4/-1

Source Android versionName from the shared app version

• Replaces the duplicated Android display version with DaqifiAppVersion so Play metadata stays aligned with telemetry.

Daqifi.Avalonia.Android/Daqifi.Avalonia.Android.csproj

Daqifi.Avalonia.Desktop.csprojSource the desktop version from shared configuration +2/-1

Source the desktop version from shared configuration

• Uses DaqifiAppVersion for the desktop head instead of a locally duplicated version literal.

Daqifi.Avalonia.Desktop/Daqifi.Avalonia.Desktop.csproj

Daqifi.Avalonia.iOS.csprojSource the iOS display version from shared configuration +4/-1

Source the iOS display version from shared configuration

• Uses DaqifiAppVersion for CFBundleShortVersionString so store metadata remains aligned with Sentry releases.

Daqifi.Avalonia.iOS/Daqifi.Avalonia.iOS.csproj

Daqifi.Avalonia.csprojApply the shared app version to the common assembly +4/-1

Apply the shared app version to the common assembly

• Versions the assembly linked by every head from DaqifiAppVersion, making it the stable metadata source for AppVersion.

Daqifi.Avalonia/Daqifi.Avalonia.csproj

Directory.Build.propsCentralize the application marketing version +27/-0

Centralize the application marketing version

• Defines DaqifiAppVersion once for the shared assembly and all platform heads while leaving store build counters independent.

Directory.Build.props

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@tylerkron
tylerkron merged commit f782a41 into main Sep 1, 2026
6 checks passed
@tylerkron
tylerkron deleted the fix/126-sentry-release-version branch September 1, 2026 00:05
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.

Sentry reports release 0.0.0 on mobile — crashes can't be tied to an app version

1 participant