From 276b048fa94229551c302479e5238f0ca2ead8f6 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Thu, 3 Sep 2026 12:24:04 +0200 Subject: [PATCH] fix(desktop): sync tauri.conf.json version to the release tag, stop swallowing install failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.1.1 was tagged but tauri.conf.json's own "version" field (what Tauri bakes into the bundle filename/internal version) stayed at 0.1.0 — the signed binary didn't match what latest.json claimed to describe, so every update download failed signature verification. desktop-updater.ts's single catch{} swallowed that identically to "offline", so it looked like nothing happened at all. release.yml now syncs tauri.conf.json's version from the git tag before building; the updater now logs a real post-accept failure instead of silently reverting. --- .gitea/workflows/release.yml | 21 +++++++++++++++++++++ apps/web/src/lib/desktop-updater.ts | 17 ++++++++++++++--- wiki/decisions/desktop-shell-tauri.md | 22 ++++++++++++++++++++++ wiki/log.md | 13 +++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a9931e7..0a35150 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -75,6 +75,27 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Sync tauri.conf.json version to the git tag + # tauri.conf.json's own "version" field is what Tauri bakes into the + # bundle filename, the app's internal version, AND the updater's + # "current vs. new" comparison — it is NOT derived from the git tag + # automatically. Hit in v0.1.1: the tag was bumped but this file + # wasn't, so the signed binary + its .sig were still built (and + # named) as 0.1.0 while latest.json (built from TAG below) claimed + # 0.1.1 — the updater found the "update", downloaded a file whose + # signature didn't match what the manifest claimed to sign, and + # silently failed (a separate bug in desktop-updater.ts's error + # handling made this invisible — also fixed). Patch it here so the + # checked-in value is only ever a placeholder for local dev builds; + # a real release's version is always driven by the tag. + run: | + set -e + VERSION="${TAG#v}" + sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" apps/desktop/src-tauri/tauri.conf.json + grep '"version"' apps/desktop/src-tauri/tauri.conf.json + env: + TAG: ${{ github.ref_name }} + - name: Build + sign desktop bundle env: # Updater signing key (Gitea repo/org secrets). Without these the diff --git a/apps/web/src/lib/desktop-updater.ts b/apps/web/src/lib/desktop-updater.ts index f738795..67ebd2c 100644 --- a/apps/web/src/lib/desktop-updater.ts +++ b/apps/web/src/lib/desktop-updater.ts @@ -41,11 +41,22 @@ export async function checkForDesktopUpdate( // Download + install the signed update (signature verified against the // pubkey in tauri.conf.json), then relaunch into the new version. - await update.downloadAndInstall(); + try { + await update.downloadAndInstall(); + } catch (err) { + // A real update WAS found and accepted — this is a genuine install + // failure (bad signature, corrupted download, disk/permission issue), + // not "offline". Surface it instead of silently reverting to the old + // version with no explanation. + console.error("desktop update download/install failed:", err); + throw err; + } const { relaunch } = await import("@tauri-apps/plugin-process"); await relaunch(); - } catch { + } catch (err) { // Offline / endpoint unreachable / no update server yet → ignore. The app - // keeps running on the current version; checking again next launch. + // keeps running on the current version; checking again next launch. Still + // log it so a real install failure (rethrown above) isn't invisible. + console.warn("desktop update check/apply skipped:", err); } } diff --git a/wiki/decisions/desktop-shell-tauri.md b/wiki/decisions/desktop-shell-tauri.md index 3f1dae0..c99c301 100644 --- a/wiki/decisions/desktop-shell-tauri.md +++ b/wiki/decisions/desktop-shell-tauri.md @@ -232,3 +232,25 @@ The desktop bundle now runs in CI under **two distinct workflows** — keep the The unsigned CI build therefore overrides it off with `--config '{"bundle":{"createUpdaterArtifacts":false}}'` (a JSON patch merged over the config), so no `.sig` is attempted and no key is required. `release.yml` keeps the config default (signs). + - **Gotcha (tag ≠ tauri.conf.json version — found + fixed 2026-09-03, v0.1.1).** The git tag + (`v0.1.1`) and `tauri.conf.json`'s own `"version"` field are two independent values with nothing + syncing them. Tauri bakes `"version"` into the bundle filename, the app's internal version, AND + what the updater compares against — NOT the git tag. Bumping only the tag (as the release + procedure implied) left the file at the prior `0.1.0`: the signed binary was built and named as + `0.1.0`, `latest.json` (built from `TAG`) correctly claimed `0.1.1`, and the updater found an + "update," downloaded it, then failed signature verification against a manifest that didn't + actually describe the file it pointed at. Compounded by a second bug (below) that made this + failure completely invisible to the operator. **Fix:** `release.yml` now has a "Sync + tauri.conf.json version to the git tag" step (`sed`-patches `"version"` from `TAG` right before + `tauri build`) — the checked-in value is now only a placeholder for local dev builds; every real + release derives its version from the tag automatically. + - **Gotcha (silent updater failure — found + fixed 2026-09-03).** `desktop-updater.ts`'s + `checkForDesktopUpdate` wrapped the ENTIRE check-download-install-relaunch sequence in one + `catch {}` that swallowed everything, by design, for the offline/no-server case. But that meant + a REAL failure after the operator already accepted the prompt (bad signature, corrupted + download, disk/permission error) failed exactly the same way as "endpoint unreachable" — no + error, no log, the app just silently reverted to the old version and re-showed the same "update + available" prompt on next launch, forever. This is what actually surfaced the tag-sync bug + above (download traffic visible, then nothing). Fixed by nesting `downloadAndInstall()` in its + own try/catch that logs and rethrows — offline/no-update still no-ops silently (outer catch), + but a failure *after* the operator accepted now logs to the console instead of vanishing. diff --git a/wiki/log.md b/wiki/log.md index 8ae09ec..b3175de 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2754,3 +2754,16 @@ mirror-repo debugging session. Fixed by setting `VITE_API_BASE=http://127.0.0.1: `tauri.conf.json`'s `beforeBuildCommand`, overriding the shared `.env.production` for the desktop build only (process env wins in Vite's load order) — verified both builds independently. Full detail on [[desktop-shell-tauri]]. + +## [2026-09-03] fix | Desktop updater silently failed: tag/version drift + swallowed install errors + +Two compounding bugs, both closed out on [[desktop-shell-tauri]] §"Desktop in CI": (1) the v0.1.1 +release bumped only the git tag — tauri.conf.json's own "version" field (what Tauri actually bakes +into the bundle filename and internal version) stayed at 0.1.0, so the signed binary didn't match +what latest.json claimed to describe, and signature verification failed on every download; (2) +desktop-updater.ts's single blanket try/catch swallowed that failure identically to "offline/no +update," so the operator saw the prompt, watched it download, then nothing — repeating forever with +zero diagnostic trail. Fixed release.yml to sed-patch tauri.conf.json's version from the git tag +right before building (checked-in value is now dev-only, never hand-maintained for releases), and +split desktop-updater.ts's catch so a real post-accept failure logs instead of vanishing. Full +detail on [[desktop-shell-tauri]].