invoke-ai / invoke-ai/launcher

Windows: launcher quits silently when the update installer fails to launch

Open
#150 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
48
Forks
23
Avg merge
1m
Merged PRs (30d)
1

Description

## Summary

On Windows, if the downloaded update installer fails to launch, the launcher quits anyway and tells the user nothing. From the user's point of view they click **Restart and Install**, the app disappears, no installer appears, and on next launch they are still on the old version with no indication anything went wrong.

This is separate from the manifest/asset name mismatch fixed in #149 — that one broke the *download*; this one is about what happens after a successful download.

## Why the user sees nothing

Traced against the installed `electron-updater@6.6.2` (`node_modules/electron-updater/out/`):

1. `src/main/updater.ts` calls `autoUpdater.quitAndInstall()` after the "Update Downloaded" dialog.
2. `BaseUpdater.quitAndInstall` (`BaseUpdater.js:12`) calls `install()`, and if it returns truthy schedules the quit: `setImmediate(() => { …; this.app.quit(); })` (`BaseUpdater.js:17`).
3. `NsisUpdater.doInstall` fires the installer **asynchronously** and then `return true` unconditionally (`NsisUpdater.js:148`) — it returns as soon as `spawn` has been called, not when it has succeeded.
4. Every spawn failure is therefore handled later, in a promise catch (`NsisUpdater.js:130-147`): `EACCES`/`UNKNOWN` retries via `elevate.exe`, `ENOENT` falls back to `shell.openPath`, anything else goes to `dispatchError`.

Those catches are microtasks, so they run *before* the `setImmediate` from step 2. By the time the failure is known, `app.quit()` is already queued and nothing in this app blocks it (`src/main/index.ts`'s `before-quit` handler is async and never calls `preventDefault`). There is no point at which a dialog can be shown and survive.

Worth stating explicitly, because it is an easy wrong turn: **adding an `autoUpdater.on('error', …)` listener does not fix this.** `AppUpdater` already registers one in its constructor (`AppUpdater.js:177`), so these errors are logged rather than thrown, and a second listener would fire in the same doomed event-loop turn. I tried exactly that and had to back it out.

## Reproduction

On Windows, with an update available:

1. Accept the update and let it download.
2. Before clicking **Restart and Install**, make the staged installer unlaunchable — delete or quarantine it in the updater cache (`%LOCALAPPDATA%\invoke-community-edition-updater\pending\`), or deny execute on it.
3. Click **Restart and Install**.

Observed: app exits, no installer, no message, version unchanged on next launch.
Expected: the user is told the update could not be installed and the app stays open.

The realistic trigger in the wild is antivirus quarantining the freshly downloaded installer between download and click, or a locked-down machine refusing both `spawn` and elevation.

## Impact

Silent, so it generates no reports of its own — users just stay on an old version and may retry the same doomed update repeatedly. The error *is* written to the log by the library's own listener (and `src/main/updater.ts:9` points `autoUpdater.logger` at `console`), so support can confirm it after the fact if logs are captured, but nothing surfaces in the UI.

## Possible directions

Not proposing a specific fix here; the constraint is that `quitAndInstall` schedules the quit itself, so any solution has to act before that call or take over from it.

- **Pre-flight the staged installer before quitting.** `await autoUpdater.downloadUpdate()` resolves to the downloaded file paths, and `UpdateDownloadedEvent.downloadedFile` (`types.d.ts:35`) carries the same. Stat/access-check it immediately before `quitAndInstall()` and report instead of quitting if it's gone or unreadable. Covers the quarantine and deleted-file cases — the common ones — but not a spawn that fails for other reasons.
- **Upstream:** `NsisUpdater.doInstall` returning `true` before the spawn resolves is arguably an electron-updater bug; the install result cannot be reported accurately by any consumer while that holds. Worth an upstream issue if we want a real fix rather than a mitigation.

## Adjacent findings from the same review

Separate from the above, and each probably its own issue if we want to act on them:

- `AppImageUpdater.js:105` floats a promise (`this.spawnLog(destination, [], env)` with no `.catch`), so a Linux AppImage spawn failure is a genuine unhandled rejection, and unlike the NSIS path it is never routed through `dispatchError`. Note the old AppImage has already been `unlinkSync`'d by that point (`AppImageUpdater.js:78`).
- The "Update Downloaded" dialog is an announcement rather than a choice: its single button's response is ignored and `quitAndInstall()` runs unconditionally, so dismissing with Esc also quits — terminating any in-flight install or generation via the `before-quit` cleanup.
- Check-side failures (offline, rate-limited, malformed manifest) are logged but never surfaced.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with src/main/updater.ts and src/main/index.ts, then inspect the documented electron-updater quitAndInstall flow and the Windows reproduction using the updater cache path. The change is done when a failed staged-installer launch keeps the launcher open and tells the user the update could not be installed, while successful installation still quits normally.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
desktop, release
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.