agalwood/Motrix · error · Error
Update is not ready to install
Error message
Update is not ready to install
What it means
This error is a state-machine precondition guard thrown by the UpdateManager's install() method. It fires when the internal state machine's phase is not exactly 'downloaded' at the moment install() is invoked. The manager wraps an electron-updater-like backend (quitAndInstall) and refuses to hand off to it until an update has actually been fetched and staged, preventing a quitAndInstall call with nothing to install.
Source
Thrown at src/main/core/update-manager.ts:183
total: 0,
},
error: undefined,
})
this.downloadPromise = this.updater
.downloadUpdate()
.catch((error: unknown) => {
this.transitionToError(error)
throw error
})
.finally(() => {
this.downloadPromise = null
})
return this.downloadPromise
}
install(beforeQuit?: () => void): void {
if (this.state.phase !== 'downloaded') {
throw new Error('Update is not ready to install')
}
beforeQuit?.()
this.updater.quitAndInstall()
}
private consumeUpdaterEvent(source: string, payload: unknown): void {
switch (source) {
case 'checking-for-update':
if (this.state.phase !== 'checking') {
this.transition({
phase: 'checking',
currentVersion: this.state.currentVersion,
})
}
break
case 'update-available': {
const info = updateInfo(payload)
if (!info.version) {View on GitHub (pinned to 1a708ee577)
Solutions
- Gate the UI element that triggers install() on the 'downloaded' phase — only enable the Install button/control after observing a state transition into 'downloaded' (subscribe to the manager's state changes or listen for the update-downloaded equivalent).
- Before calling install(), read this.state.phase (or a public getter exposing it) and early-return/skip if it is not 'downloaded'; log the actual phase for diagnostics.
- Ensure download() is awaited or its promise resolved before install() is reachable — chain install() off the same downloadPromise the manager returns, or off an event emitted from a successful consumeUpdaterEvent for the 'download-complete' source.
- If a download failed, drive the manager back through checkForUpdate()+download() and wait for the 'downloaded' phase before re-attempting install(); do not retry install() against an errored phase.
- Audit consumeUpdaterEvent to confirm the source that corresponds to a completed download actually transitions phase to 'downloaded' — a missing case there means the phase never reaches the install-allowed state.
Example fix
// before — fires as soon as an update is known to exist
updater.on('update-available', () => {
updater.install() // throws: phase is 'checking' / 'downloading', not 'downloaded'
})
// after — wait for the downloaded phase, then install
updater.on('update-available', () => {
updater.download()
})
updater.onStateChange((state) => {
if (state.phase === 'downloaded') {
updater.install()
}
}) Defensive patterns
Strategy: validation
Validate before calling
// Read the manager's phase before attempting install.
// Assumes a public getter; if none exists, expose one rather than reaching into state.
function safeInstall(manager: UpdateManager, beforeQuit?: () => void): void {
if (manager.state.phase !== 'downloaded') {
console.warn(`install() skipped: phase is '${manager.state.phase}', expected 'downloaded'`)
return
}
manager.install(beforeQuit)
} Type guard
// Narrow on the phase before calling install.
type DownloadedState = { phase: 'downloaded' }
function isDownloaded(state: { phase: string }): state is DownloadedState {
return state.phase === 'downloaded'
}
// Usage:
if (isDownloaded(manager.state)) {
manager.install()
} Try / catch
// install() throws synchronously, so wrap the call site (not the download).
try {
manager.install(() => { /* beforeQuit cleanup */ })
} catch (err) {
if (err instanceof Error && err.message === 'Update is not ready to install') {
// Re-check phase and either kick off a download or surface a UI hint.
console.warn('Install rejected — current phase:', manager.state.phase)
return
}
throw err
} Prevention
- Drive the Install affordance from a state-change subscription, not from the update-available event — 'available' is not 'downloaded'.
- Expose manager.state.phase (or a canInstall() helper) and have the UI bind Install's enabled state to it, so the call is structurally unreachable otherwise.
- Treat a download failure as a phase reset: on 'error' phase, disable Install and require a fresh checkForUpdate()+download() cycle before re-enabling.
- Never call install() twice without an intervening successful download; the first attempt (if it somehow proceeds) nulls downloadPromise and mutates phase.
- Add an assertion/log at every install() call site recording the phase, so a misfire is diagnosable instead of a bare throw.
When it happens
Trigger: install(beforeQuit?) is called when this.state.phase is anything other than 'downloaded' — e.g. 'idle', 'checking', 'downloading', 'error', or 'available' (update known but not yet pulled). This happens when a caller invokes install() right after checkForUpdate() resolves with an available update but before download() has completed, or when install() is called a second time after a failed/interrupted download that transitioned the phase back to an error/idle state.
Common situations: Wiring an 'Install now' button to fire immediately on the 'update-available' event instead of waiting for the 'update-downloaded' event; calling install() in a menu item that is enabled as soon as a version delta is detected; a download that silently failed (network drop, signature mismatch) leaving the phase at 'error' while the UI still shows an Install button; race where the user clicks Install during the checking phase; calling install() after the app already auto-launched a download but the downloadPromise rejected and was nulled in the finally block.
Related errors
- ${label} is not a legal task status
- kind is not a legal history event kind
- toStatus is required
- Added must not have a fromStatus
- Started must enter an active status
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/70c8e8cccde24898.
Report an issue: GitHub.