Skip to content

fix: harden desktop updater handoff - #37748

Open
AJV20 wants to merge 1 commit into
NousResearch:mainfrom
AJV20:fix/desktop-update-fallback
Open

fix: harden desktop updater handoff#37748
AJV20 wants to merge 1 commit into
NousResearch:mainfrom
AJV20:fix/desktop-update-fallback

Conversation

@AJV20

ghost commented Jun 3, 2026

Copy link
Copy Markdown

Summary

  • Validate the staged desktop updater before treating it as usable.
  • Catch asynchronous updater spawn failures so the desktop does not quit after a failed handoff.
  • Fall back to the existing in-app POSIX update path when the staged updater is missing or cannot launch.
  • Add focused Node tests for updater handoff validation/spawn behavior.

Test Plan

  • node --check electron/main.cjs
  • npx eslint electron/updater-handoff.cjs electron/updater-handoff.test.cjs
  • npm run test:desktop:platforms

Note: full npm run lint still reports pre-existing no-empty issues in electron/main.cjs; this change does not introduce those.

@AJV20
AJV20 requested a review from a team June 3, 2026 00:31
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have labels Jun 3, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Overview

Harden desktop updater handoff by improving robustness of the update process. Well-scoped fix.

✅ Looks Good

  • 140 additions, 6 deletions
  • Makes the desktop updater handoff more resilient
  • Companion to #37789 (remote mode update skip)
  • No security concerns
  • Clean implementation

Reviewed by Hermes Agent

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes - the hardening is valuable but this PR overlaps and conflicts with #38955 on the same applyUpdates gate, and the two encode contradictory routing.

What is good and verified against main: resolveUpdaterBinary() currently does a bare fileExists(candidate) check, so a present-but-non-executable/stale macOS hermes-setup is treated as runnable. Replacing that with isRunnableUpdaterBinary (statSync isFile + X_OK on POSIX, exists on Windows) is a real, independent improvement, as is waitForUpdaterSpawn gating the hand-off on the child actually spawning (with a timeout fallback for detached children that never emit spawn) and the POSIX fallback to applyUpdatesPosixInApp on launch failure. The new updater-handoff.cjs unit tests are solid.

The conflict: this PR keeps the if (!updater && !IS_WINDOWS) routing and only changes the opts plumbing, whereas #38955 rewrites that same gate to if (!IS_WINDOWS) to kill the macOS restart loop. They touch the identical lines and will not merge cleanly. More importantly, #37748’s resolveUpdaterBinary hardening makes a staged-but-broken macOS binary resolve to null - which only changes macOS behavior if the !updater gate still applies on macOS. Under #38955 macOS never consults updater at all, so the two are partially redundant on macOS.

Recommendation: land #38955 first (canonical macOS routing fix), then rebase this PR to drop the routing/opts churn and keep ONLY the Windows-relevant pieces: isRunnableUpdaterBinary (guards a stale hermes-setup.exe on the Windows hand-off path) and waitForUpdaterSpawn + the Windows error surface. After that this is an easy approve.

@AJV20

AJV20 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Updated this branch per the requested-changes guidance to avoid overlapping with the canonical macOS routing fix.

Changes:

Verification:

  • node --check apps/desktop/electron/main.cjs
  • node --check apps/desktop/electron/updater-handoff.cjs
  • node --test apps/desktop/electron/updater-handoff.test.cjs → 5 passed
  • npm --prefix apps/desktop run test:desktop:platforms → 22 passed
  • git diff --check

Pushed head: d7391a84b

@AJV20

AJV20 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Final refresh pushed after latest main moved. Head is now 5ea6f7429; GitHub reports the PR is mergeable. Re-ran node --test apps/desktop/electron/updater-handoff.test.cjs (5 passed), npm --prefix apps/desktop run test:desktop:platforms (170 passed, 1 skipped), node --check for touched Desktop files, and git diff --check.

@AJV20

AJV20 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Pushed another update for the requested-change scope cleanup.

What changed:

  • removed the POSIX executable-bit helper/test coverage from this PR
  • renamed the helper to isRunnableWindowsUpdaterBinary so the diff is explicitly Windows-only
  • removed unrelated type-check package-script churn from the PR diff
  • kept macOS/POSIX updater routing untouched (resolveUpdaterBinary() still uses the existing fileExists() path outside Windows)

Verification rerun locally:

  • node --check apps/desktop/electron/main.cjs
  • node --check apps/desktop/electron/updater-handoff.cjs
  • node --test apps/desktop/electron/updater-handoff.test.cjs → 4 passed
  • npm --prefix apps/desktop run test:desktop:platforms → 169 passed, 1 skipped
  • git diff --check

Head: f25e4e8b1

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for narrowing this to the Windows handoff case. The failure-handling goal remains relevant, but this branch needs a TypeScript salvage rather than a direct merge.

Problems

  • apps/desktop/electron/main.ts:1318-1323 already defines fileExists() as fs.statSync(...).isFile(), and resolveUpdaterBinary() uses it at :2277-2282. The new CJS isRunnableWindowsUpdaterBinary() therefore does not add validation beyond current behavior.
  • The proposed spawn check covers only applyUpdates(). handOffWindowsBootstrapRecovery() resolves the same updater at main.ts:2639, spawns it at :2669-2679, then writes its marker and schedules quit at :2683-2699 without observing an asynchronous spawn error.
  • Current main migrated this surface from CJS to TypeScript in 39d09453f95e8aefc0c97e5d9b30ff341cae9ed8; main.cjs and the proposed CJS helper/test paths are gone.

Suggested changes

  • Port a shared Windows handoff check to TypeScript and apply it before marker/quit behavior in both handoff call paths.
  • Reuse fileExists() rather than duplicating its regular-file predicate, and cover emitted spawn errors in the TypeScript platform suite.

Automated hermes-sweeper review.


try {
const stat = fsModule.statSync(candidate)
return stat.isFile()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileExists() already performs this exact statSync(...).isFile() validation in the updater resolver, so this new predicate does not change behavior. On current main that implementation is at apps/desktop/electron/main.ts:1318-1323; please avoid duplicating it when porting the useful spawn-error handling.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@AJV20
AJV20 force-pushed the fix/desktop-update-fallback branch from fe432a8 to abc3074 Compare July 15, 2026 13:14
@AJV20

AJV20 commented Jul 15, 2026

Copy link
Copy Markdown
Author

Clean TypeScript salvage pushed on current main as abc307411.

  • Shared Windows updater spawn validation now gates both applyUpdates() and bootstrap recovery before marker/quit behavior.
  • Kept existing fileExists() resolution and POSIX routing unchanged; removed the obsolete CJS/duplicate-validation overlap.

Validation:

  • npm --prefix apps/desktop run test:desktop:platforms — 38 files passed, 417 passed / 1 skipped
  • npm --prefix apps/desktop run typecheck — passed
  • npx eslint electron/updater-handoff.ts electron/updater-handoff.test.ts — passed
  • git diff --check — passed

@AJV20
AJV20 force-pushed the fix/desktop-update-fallback branch from abc3074 to 66d9bf8 Compare July 15, 2026 13:20
@AJV20

AJV20 commented Jul 15, 2026

Copy link
Copy Markdown
Author

Clean TypeScript salvage finalized on current main at 66d9bf80a.

  • Shared Windows updater spawn validation gates both applyUpdates() and handOffWindowsBootstrapRecovery() before marker writes and quit scheduling.
  • Existing fileExists() updater resolution is unchanged; the redundant CJS predicate and obsolete overlap are absent.
  • POSIX spawn/handoff behavior remains unchanged.

Validation:

  • npm --prefix apps/desktop run test:desktop:platforms — 38 files passed, 417 passed / 1 skipped
  • npm --prefix apps/desktop run typecheck — passed
  • npx eslint electron/updater-handoff.ts electron/updater-handoff.test.ts — passed
  • git diff --check — passed

GitHub API reports one commit / three changed files, mergeable and rebaseable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Assignees

Couldn't load assignees.