Summary
safeCheck() in src/main/updater.ts swallows a rejected autoUpdater.checkForUpdates() in its catch block (logs to console only) without pushing any terminal event to the renderer. When a manual "Check for updates" hits such a failure, the renderer never receives a finishManual callback, so the checking spinner stays stuck forever and the button remains disabled ("Checking…").
This is a pre-existing issue, surfaced by contrast during review of #25: the new unsupported branch correctly sends a terminal event, whereas the catch branch does not.
Reproduction
- Run a packaged build that does ship
app-update.yml (e.g. a real DMG), or any build with a reachable feed config.
- Go offline (or otherwise make the GitHub feed fail in a way that rejects the promise but does not fire
autoUpdater's 'error' event — e.g. a synchronous config/parse error).
- Open Settings → About → click "Check for updates".
Expected: the check ends with an inline "check failed" note and the button re-enables.
Actual: the spinner is stuck on "Checking…" indefinitely and the button stays disabled.
Cause
// src/main/updater.ts
async function safeCheck(): Promise<void> {
if (!hasUpdateFeed()) {
send(UPDATER_IPC.unsupported)
return
}
try {
await autoUpdater.checkForUpdates()
} catch (err) {
console.error('[updater] check failed:', err)
// no send() here -> renderer's finishManual() never runs -> spinner never clears
}
}
autoUpdater's own 'error' event often fires too and would drive finishManual('error'), but the promise rejection and the 'error' event are not guaranteed to be paired on every failure path, so the manual check can still hang.
Suggested fix
Emit a terminal event in the catch so the renderer can always end the manual check:
} catch (err) {
console.error('[updater] check failed:', err)
send(UPDATER_IPC.error)
}
Consider de-duping against the autoUpdater.on('error', …) handler so a single failure does not surface two error notes.
Summary
safeCheck()insrc/main/updater.tsswallows a rejectedautoUpdater.checkForUpdates()in itscatchblock (logs to console only) without pushing any terminal event to the renderer. When a manual "Check for updates" hits such a failure, the renderer never receives afinishManualcallback, so thecheckingspinner stays stuck forever and the button remains disabled ("Checking…").This is a pre-existing issue, surfaced by contrast during review of #25: the new
unsupportedbranch correctlysends a terminal event, whereas thecatchbranch does not.Reproduction
app-update.yml(e.g. a real DMG), or any build with a reachable feed config.autoUpdater's'error'event — e.g. a synchronous config/parse error).Expected: the check ends with an inline "check failed" note and the button re-enables.
Actual: the spinner is stuck on "Checking…" indefinitely and the button stays disabled.
Cause
autoUpdater's own'error'event often fires too and would drivefinishManual('error'), but the promise rejection and the'error'event are not guaranteed to be paired on every failure path, so the manual check can still hang.Suggested fix
Emit a terminal event in the
catchso the renderer can always end the manual check:Consider de-duping against the
autoUpdater.on('error', …)handler so a single failure does not surface two error notes.