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

  1. 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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

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


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/70c8e8cccde24898. Report an issue: GitHub.