← All writing

Technical note · 2026-05-20

Desktop auto-update failed: residual processes and recoverable state

Focus on the failure path: stop local services that hold files or ports before install, restore what was stopped on failure, and distinguish check, download and pending-restart states in the UI.

  • Tauri
  • Desktop updates
  • Process lifecycle
  • Failure recovery

Related case: AI Agent application platform. This note follows the control flow of a local Tauri update service. Examples omit business command names and endpoints and only express the order of stop, install, restart and fallback. It does not claim that any live incident was automatically recovered.

Update is not “download finished”

People often treat auto-update as check → download → install → restart. The fragile part is the local state in between:

  • the app itself is still running while the installer wants to replace occupied files;
  • local services started earlier (MCP, gateways, helpers) may hold ports or directories;
  • if download or install fails, a single “update failed” message does not say whether the old version still works;
  • on Windows, leftover processes can keep the next launch pointed at stale resources.

So narrow the question: on failure, does the system leave an explainable state the user can continue from?

Stop resource-holding processes before install

Before calling install, the local update service stops auxiliary services it started and records that they were stopped. The point is not cosmetic cleanliness:

  1. fewer file locks during install;
  2. a clear list of processes to restore after failure, instead of blindly restarting everything.
Check update ─→ stop local services (record stopped) ─→ download/install

                          fail ←─────────────────────────┴─────────────→ success → relaunch

                            └─ restore recorded services, then give an actionable message

Do not reverse this order. Installing first makes leftover processes more likely to race the installer for the same directory.

Separate failure branches instead of one generic error

At least three branches deserve distinct handling:

Failure point What the user needs to know What the UI should not do
Check failed network or endpoint issue; old version still usable imply a new version is ready
Download/install interrupted whether partial files were written; whether to retry auto-restart into a half-written build
Installed but not restarted new version is on disk; user should confirm restart force-kill unsaved work silently

Locally, a Tauri Updater check failure falls back to opening a manual installer link. That is not two parallel update strategies; it is one intent with a fallback, and the fallback still restores services that were stopped.

Version strings are not process state

“Up to date” only means the version comparison passed. It does not prove that:

  • auxiliary processes from the last update have exited;
  • temporary or _up_-style upgrade directories were cleaned;
  • frontend assets loaded in the window match the executable version.

Failure messages should therefore carry checkable fields: current version, target version, failure stage, and whether local services were stopped. Start with those fields before choosing retry, manual install, or a cold start after clearing leftovers.

Test failure paths, not only successful upgrades

The happy path almost always passes. More useful cases:

  • check endpoint timeout: keep the old version usable and label the error clearly;
  • download interrupted: do not relaunch; restore stopped services;
  • install rejected by locked files: tell the user what to close instead of silent retry loops;
  • window recovery after identity switch or forced reconnect: rebuildable page state after restart.

Reliability of auto-update is measured first by whether the user still knows what state the app is in after a failure—and only then by whether one click installs the new version.