fix(desktop): apply the stale-installer marker guard to both hand-offs
Both hand-off sites pre-write the update marker: the in-app Update button (applyUpdates) and the Windows bootstrap-recovery path (handOffWindowsBootstrapRecovery). Either one can strand a user on a pre-#74782 staged installer, and the recovery path is worse — it fires when the install is already unhealthy, so a refused claim there wedges the very repair meant to heal it. Route both through stagedUpdaterSupportsPrewrittenMarker and log the skip so the reason is visible in desktop.log instead of looking like a missing write. Also document on copy_self_to_hermes_home that its --update no-op is what lets an installer-protocol change strand the entire installed base on a binary that predates it — the root enabler of this class of bug.
This commit is contained in:
@@ -98,6 +98,12 @@ pub fn update_in_progress_marker() -> PathBuf {
|
||||
/// that path), where copying onto ourselves would be a Windows sharing
|
||||
/// violation. Best-effort: a failure here must not fail the install, so the
|
||||
/// caller logs and continues.
|
||||
///
|
||||
/// NOTE: because of that no-op, a user's staged installer is only ever written
|
||||
/// by a full install/repair. Every later `--update` runs the ORIGINAL binary,
|
||||
/// so an installer-protocol change can strand the whole installed base on a
|
||||
/// binary that predates it (see `restage_from_checkout`, which repairs this
|
||||
/// from the freshly-updated checkout).
|
||||
pub fn copy_self_to_hermes_home() -> std::io::Result<()> {
|
||||
let src = std::env::current_exe()?;
|
||||
let dest = installer_dest();
|
||||
|
||||
@@ -201,7 +201,11 @@ import {
|
||||
sandboxPreflight
|
||||
} from './update-relaunch'
|
||||
import { isOfficialSshRemote, OFFICIAL_REPO_HTTPS_URL } from './update-remote'
|
||||
import { resolveStagedUpdaterBinary, spawnUpdaterProcess } from './updater-process'
|
||||
import {
|
||||
resolveStagedUpdaterBinary,
|
||||
spawnUpdaterProcess,
|
||||
stagedUpdaterSupportsPrewrittenMarker
|
||||
} from './updater-process'
|
||||
import { formatBlockerMessage, formatProbeFailedMessage, scanVenvBlockers } from './venv-blocker-scan'
|
||||
import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'
|
||||
import { createWakeIndicatorWindowController } from './wake-indicator-window'
|
||||
@@ -3012,8 +3016,20 @@ async function applyUpdates(opts = {}) {
|
||||
// the venv. By writing the marker ourselves the renderer's
|
||||
// waitForUpdateToFinish() gate sees a live update and parks instead.
|
||||
// The updater overwrites this with its own PID later; same format.
|
||||
if (Number.isInteger(child.pid)) {
|
||||
//
|
||||
// SKIPPED for pre-#74782 staged updaters: those have no self-PID
|
||||
// exclusion, so they read this very marker as a foreign live owner and
|
||||
// abort with "Another Hermes update is already running (PID <itself>)" —
|
||||
// an unbreakable loop, because the update that would replace the stale
|
||||
// binary is the one being refused. Losing the anti-respawn hardening is
|
||||
// strictly better than never updating again, and the updater still writes
|
||||
// its own marker moments later.
|
||||
if (Number.isInteger(child.pid) && stagedUpdaterSupportsPrewrittenMarker(updater)) {
|
||||
writeUpdateMarker(HERMES_HOME, child.pid)
|
||||
} else if (Number.isInteger(child.pid)) {
|
||||
rememberLog(
|
||||
`[updates] skipping marker pre-write: staged updater predates self-adopt (${updater}); it would refuse its own claim`
|
||||
)
|
||||
}
|
||||
|
||||
rememberLog(`[updates] launched updater: ${updater} ${updaterArgs.join(' ')}; exiting desktop to release venv shim`)
|
||||
@@ -3100,9 +3116,15 @@ async function handOffWindowsBootstrapRecovery(reason) {
|
||||
|
||||
// Same marker pre-write as applyUpdates — see comment there. The recovery
|
||||
// hand-off has the same window where the renderer can respawn a backend
|
||||
// before the updater writes its own marker.
|
||||
if (Number.isInteger(child.pid)) {
|
||||
// before the updater writes its own marker, and the same stale-updater
|
||||
// exclusion: a pre-#74782 binary would refuse its own pre-written claim and
|
||||
// strand the very recovery meant to heal the install.
|
||||
if (Number.isInteger(child.pid) && stagedUpdaterSupportsPrewrittenMarker(updater)) {
|
||||
writeUpdateMarker(HERMES_HOME, child.pid)
|
||||
} else if (Number.isInteger(child.pid)) {
|
||||
rememberLog(
|
||||
`[bootstrap] skipping marker pre-write: staged updater predates self-adopt (${updater}); it would refuse its own claim`
|
||||
)
|
||||
}
|
||||
|
||||
rememberLog(
|
||||
|
||||
Reference in New Issue
Block a user