From 64c1db961f6c7b0fa514777b33f3b2c56e559b3e Mon Sep 17 00:00:00 2001 From: chelsealong Date: Sat, 1 Aug 2026 02:10:33 +0000 Subject: [PATCH] fix(desktop): close the same handoff race in bootstrap recovery handOffWindowsBootstrapRecovery() writes the update marker unconditionally, same as applyUpdates() before the previous commit. It's reachable during boot whenever resolveHermesBackend reports bootstrap-needed, which a relaunch mid-update can plausibly trigger on Windows -- clobbering a live updater's marker through this second path. Apply the same updateHandoffConflict() guard here: refuse to spawn a second updater when one is already alive, and quit instead so the live updater can finish and restart us. --- apps/desktop/electron/main.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 65395c499db77..744c8912b75d8 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -3044,6 +3044,24 @@ async function handOffWindowsBootstrapRecovery(reason) { return false } + const handoffConflict = updateHandoffConflict(HERMES_HOME) + + if (handoffConflict) { + // Same hazard as applyUpdates (#75778): a live foreign updater already + // owns the marker. Spawning another here would overwrite its claim and + // race a second updater over the same install tree. The live updater + // is already working on this exact install and will restart us when + // it finishes, so treat this the same as a successful hand-off instead + // of clobbering it with our own. + rememberLog(`[bootstrap] refusing recovery hand-off: ${handoffConflict.message}`) + isQuittingForHandoff = true + setTimeout(() => { + app.quit() + }, UPDATE_HANDOFF_DWELL_MS) + + return true + } + const updateRoot = resolveUpdateRoot() const { branch: configuredBranch } = readDesktopUpdateConfig()