From 630baa6c18b0d88d0b0f768ef907ce8b4a3e5471 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:42:30 -0300 Subject: [PATCH] fix(electron): swallow auto-updater check rejection to avoid unhandled rejection (#3339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkForUpdates() is fired unawaited from a setTimeout at startup. The underlying autoUpdater.checkForUpdates() rejects on a 404 (release update manifest not published yet), offline, or rate-limit — and the uncaught rejection surfaced as an "Unhandled Rejection", which the packaged-app smoke test treats as fatal (failed the macOS-intel v3.8.13 build; passed elsewhere only by timing race). The autoUpdater "error" event still notifies the user; wrap the await so the promise rejection never escapes. Adds a regression test. --- electron/main.js | 13 ++++++++++++- tests/unit/electron-main.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/electron/main.js b/electron/main.js index ee0e22a783..b6e8f99514 100644 --- a/electron/main.js +++ b/electron/main.js @@ -266,7 +266,18 @@ async function checkForUpdates(silent = false) { } return; } - await autoUpdater.checkForUpdates(); + // Update-check failures (404 when the release manifest isn't published yet, + // offline, rate-limited) are surfaced to the user via the autoUpdater "error" + // event handler. The promise returned by checkForUpdates() ALSO rejects on + // those, so it must be caught here — the startup check (line ~928) fires it + // unawaited inside a setTimeout, and an uncaught rejection there becomes an + // "Unhandled Rejection" that the packaged-app smoke test treats as fatal. + try { + await autoUpdater.checkForUpdates(); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + console.warn("[Electron] Update check failed (non-fatal):", msg); + } } async function downloadUpdate() { diff --git a/tests/unit/electron-main.test.ts b/tests/unit/electron-main.test.ts index 8ccacc7ddd..7ba0746f22 100644 --- a/tests/unit/electron-main.test.ts +++ b/tests/unit/electron-main.test.ts @@ -76,6 +76,36 @@ describe("Electron packaging manifest completeness (#3292)", () => { } }); +// ─── Auto-updater unhandled-rejection guard (v3.8.13) ──────── +// +// checkForUpdates() is fired unawaited from a setTimeout at startup. The +// underlying autoUpdater.checkForUpdates() rejects on a 404 (no published +// update manifest yet), offline, or rate-limit — and an uncaught rejection +// there surfaces as an "Unhandled Rejection" that the packaged-app smoke test +// treats as fatal (it failed the macOS-intel build for v3.8.13). The call must +// be wrapped so the rejection never escapes. + +describe("Electron auto-updater rejection handling (v3.8.13)", () => { + const mainSrc = readFileSync(join(import.meta.dirname, "../../electron/main.js"), "utf8"); + + it("wraps autoUpdater.checkForUpdates() so a 404/offline check can't become an unhandled rejection", () => { + const fn = mainSrc.match(/async function checkForUpdates\([\s\S]*?\n}/); + assert.ok(fn, "checkForUpdates function should exist in electron/main.js"); + const body = fn![0]; + assert.match(body, /try\s*\{/, "checkForUpdates must wrap the update check in try/catch"); + assert.match( + body, + /catch\s*\([\s\S]*?\)\s*\{/, + "checkForUpdates must catch the rejecting autoUpdater.checkForUpdates() call" + ); + assert.match( + body, + /try[\s\S]*await autoUpdater\.checkForUpdates\(\)[\s\S]*catch/, + "autoUpdater.checkForUpdates() must sit inside the try block" + ); + }); +}); + // ─── URL Validation Tests ──────────────────────────────────── describe("Electron URL Validation", () => {