Skip to content

fix(updater): silence platform-missing errors + serialize latest.json - #31

Open
playmaker wants to merge 1 commit into
lovstudio:mainfrom
playmaker:fix/updater-silent-platform-error
Open

fix(updater): silence platform-missing errors + serialize latest.json#31
playmaker wants to merge 1 commit into
lovstudio:mainfrom
playmaker:fix/updater-silent-platform-error

Conversation

@playmaker

@playmaker playmaker commented Jul 23, 2026

Copy link
Copy Markdown

Problem

macOS (Apple Silicon) users launching the desktop app see a "Update check failed" toast in the bottom-right corner on every startup, even though they already have the latest release installed. The toast body reads roughly:

None of the fallback platforms `[2]: darwin-aarch64-app, darwin-aarch64`
were found in the response `platforms` object

There are two distinct bugs that compound to produce this UX:

Bug 1 — root cause: race in the release workflow

.github/workflows/release.yml runs four targets in parallel (matrix over linux + macOS x64 + macOS aarch64 + Windows). Each tauri-action invocation is configured with includeUpdaterJson: true, so every slot uploads its own latest.json to the same GitHub Release. Last writer wins.

Concretely, the v0.40.0 release assets on disk include:

timestamp (UTC) asset from which slot
03:13:42 lovcode_0.40.0_aarch64.dmg macOS aarch64
03:13:47-51 lovcode_aarch64.app.tar.gz + .sig macOS aarch64
03:19:14-20 lovcode_0.40.0_x64_en-US.msi + _x64-setup.exe + sigs windows
03:35:41-59 _amd64.AppImage / .deb / .rpm + sigs linux
03:36:01 latest.json containing only windows-* and linux-* keys (no darwin-*) whoever ran last

All seven .sig files are present on the release — including lovcode_aarch64.app.tar.gz.sig and lovcode_x64.app.tar.gz.sig. They just never made it into latest.json. The updater client (tauri-plugin-updater 2.10) walks the candidate list {os}-{arch}-{installer}, {os}-{arch} in get_urls (see updater.rs:581-597) and falls back to Error::TargetsNotFound when neither key is present — exactly the error the user sees at startup.

Bug 2 — UX blows the symptom up

src/components/UpdateChecker.tsx:91-97 catches the rejection from check() and unconditionally stores e.message into state.error, which then renders as a "Update Check Failed" toast. Even when the underlying cause is "this release doesn't ship your platform" (a perfectly normal condition during a partial rollout), the UI pretends something broke.

Fix

1. src/components/UpdateChecker.tsx — silence the platform-missing class

Add a tiny classifier that recognises only the two tauri-plugin-updater variants whose message text mentions fallback platforms or was not found in the response \platforms` object (Error::TargetNotFoundandError::TargetsNotFoundin 2.10). When one of those fires, transition tostage: "latest"instead ofstage: "error". The card's canShowalready excludeslatest, so it never appears; StatusBarreadslatest` as "you are up to date" which matches reality.

All other error classes — IO failures, timeouts, signature mismatches, releaseNotFound, etc. — still surface the toast. Network and signature problems are exactly the kind of failure users need to see.

2. .github/workflows/release.yml + scripts/publish-update-json.cjs — deterministic latest.json

Add a publish-json job that runs after the build matrix completes. It:

  1. Resolves the release tag (push event: from release.outputs.tag; manual dispatch: from inputs.tag).
  2. Downloads every *.sig already on the release with gh release download --pattern '*.sig'.
  3. Runs scripts/publish-update-json.cjs, which:
    • reads version from package.json,
    • reads pub_date from gh release view $TAG --json publishedAt,
    • builds the platforms map by matching each platform key to its known bundle filename using the signature contents pulled in step 2,
    • writes latest.json and uploads it via gh release upload --clobber.
  4. Logs the resulting key set on stdout for easy diffing.

The matrix slots keep their existing includeUpdaterJson: true and parallel builds — timing is unchanged. The publish-json job then deterministically overwrites whatever one of them wrote last, eliminating the race.

The platform → filename mapping in the script mirrors what tauri-action emits today:

darwin-aarch64, darwin-aarch64-app          ← lovcode_aarch64.app.tar.gz
darwin-x86_64,  darwin-x86_64-app           ← lovcode_x64.app.tar.gz
linux-x86_64, linux-x86_64-appimage         ← lovcode_<ver>_amd64.AppImage
linux-x86_64-deb                            ← lovcode_<ver>_amd64.deb
linux-x86_64-rpm                            ← lovcode-<ver>-1.x86_64.rpm
windows-x86_64, windows-x86_64-msi          ← lovcode_<ver>_x64_en-US.msi
windows-x86_64-nsis                         ← lovcode_<ver>_x64-setup.exe

11 keys total. Each .tar.gz is reused twice (once bare, once with the -app / -nsis etc suffix) the way tauri-action does, so tauri-plugin-updater's {os}-{arch}-{installer} and {os}-{arch} candidates both resolve.

Dry-run against the actual v0.40.0 release (gh release download v0.40.0 --pattern '*.sig') produces exactly those 11 keys with the right URLs and signatures.

Verification

  • node --check scripts/publish-update-json.cjs passes.
  • The mapping table above is driven by feeding real .sig files from v0.40.0 (already on gh release download) into the script's logic — output matches expectations.
  • After merge, the next push-to-main release run will exercise publish-json end-to-end; the workflow log will print latest.json uploaded: keys=darwin-aarch64, darwin-aarch64-app, darwin-x86_64, darwin-x86_64-app, linux-x86_64, linux-x86_64-appimage, linux-x86_64-deb, linux-x86_64-rpm, windows-x86_64, windows-x86_64-msi, windows-x86_64-nsis.
  • After merge, a macOS aarch64 user restarting the app sees no toast (because the release's latest.json now has darwin-aarch64 + darwin-aarch64-app); meanwhile a real network failure or signature mismatch still produces the toast.

Out of scope / not touched

  • tauri.conf.json, Cargo.toml, Cargo.lock.
  • The build matrix steps themselves (each slot still runs tauri-action and uploads its own bundle + .sig; only the final latest.json is centralised).
  • macOS x64 custom fallback path (lines 242-289).
  • downloadAndInstall's catch — its errors only happen after the user explicitly clicks "Update", never at startup.
  • Re-publishing a corrected latest.json for v0.40.0 on the existing release. The script can do this manually after merge (TAG=v0.40.0 GITHUB_REPOSITORY=lovstudio/lovcode node scripts/publish-update-json.cjs), but that's a separate operational fix and not part of this PR.

🤖 Generated with Claude Code

Two related fixes for the Tauri auto-updater flow:

- src/components/UpdateChecker.tsx: when the latest.json response
  doesn't carry an entry for the current platform (tauri-plugin-updater
  Error::TargetNotFound / Error::TargetsNotFound), treat it as 'no
  update available' instead of flashing a 'check failed' toast. IO,
  timeout, signature, and other errors still surface as before.

- .github/workflows/release.yml + scripts/publish-update-json.cjs:
  the matrix build jobs each emit their own latest.json in parallel;
  last writer wins and platform entries get dropped. Add a post-build
  publish-json job that downloads every *.sig from the release,
  maps them to the expected platform keys, and overwrites
  latest.json deterministically.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant