0056: Windows updates over running programs

On this page

Status: accepted

Context

On Windows the app starts leashd.exe itself, and it keeps serving after the app quits; leash mcp runs under editors; and the updater exits the app with std::process::exit, so Pair's helper isn't stopped either. Tauri's installer closes only the app (short-leash-app.exe, through Restart Manager), so an update stopped at "Error opening file for writing" on leashd.exe. Ignore left the old background service in place behind a newer app. Windows won't let a running program's file be replaced, but it lets that file be renamed.

Decision

The installer's NSIS_HOOK_PREINSTALL (src-tauri/windows/hooks.nsh) runs before any file is copied. For each of leashd.exe, leash.exe and leash-pair.exe, and every program and DLL in engines\voice\ and engines\llm\ (the AI helpers and their engines, 0060; a loaded DLL is locked like a running program), it:

  • deletes copies moved aside by earlier updates (<name>.*.old), which fails harmlessly for any still running;
  • tries to open the program for writing, and if that fails because it's running, renames it to the first free <name>.<n>.old (n from 1 to 9) and copies it back under its own name. Nothing runs that copy, so the installer replaces it. The hook runs before Tauri's "Short Leash is running" prompt, and if the install stops there (Cancel) or anywhere before the new file is written, the old program is still in place.

Nothing is stopped by the installer. The old leashd keeps serving until the relaunched app finds it on another version (ensure_daemon in src-tauri/src/lib.rs), asks it to shut down through the API, and starts the new one: the same path an update takes on macOS. The old daemon stops answering at once but holds Short Leash's lock while it stops its services, and a new leashd started meanwhile leaves it to serve and exits. So the app starts leashd again whenever the one it started has exited without answering, for up to 30 seconds.

CI builds the installer on Windows and runs scripts/test-installer-update.ps1, which installs, starts stand-ins from leashd.exe, leash.exe, leash-pair.exe and both AI helpers (one holding an engine DLL open), installs again with /UPDATE, and checks that each install succeeds, that each was replaced, moved aside and left running, and that a later install and uninstalling clear them.

Consequences

  • Updates go through with leashd, leash mcp or Pair's helper running, and an install over a background service left behind by an earlier failed update replaces it.
  • Sites stay up during the install. Services stop the usual way when the app restarts leashd, not through a forced kill, which would leave databases to recover.
  • Copies moved aside take disk space (about 25 MB each) until the next install or uninstall (NSIS_HOOK_PREUNINSTALL deletes them too) after whatever ran them has quit.
  • Rejected: registering the sidecars with Restart Manager as Tauri does for the app, because its forced shutdown kills leashd without stopping the services it runs. Also rejected: asking leashd to shut down from the updater's on_before_exit, because that covers neither installs started from a downloaded installer nor leash mcp.
  • If all nine slots are taken by programs still running, the installer falls back to its usual "Error opening file for writing" prompt.