You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(updater): shut down the VSS helper before applying an update (#126)
Closes#125.
## Problem
During 2.0.0 release QA, applying an app update failed with the NSIS
installer error `Error opening file for writing:
...\driven-vss-helper.exe` (Abort/Retry/Ignore) when the elevated VSS
helper broker was running.
## Root-cause trace
- On Windows, `tauri-plugin-updater`'s `download_and_install` runs the
**NSIS installer synchronously** with `/P /R` (passive + restart) - it
overwrites the bundle files, including the bundled
`driven-vss-helper.exe` sidecar, in that call.
- Tauri's NSIS `Section Install` runs `NSIS_HOOK_PREINSTALL`, then
`CheckIfAppIsRunning "${MAINBINARYNAME}.exe"` - it only terminates the
**main** binary (`driven-app.exe`), **never the sidecar**. A running
elevated helper holds an open handle to its own exe, so the overwrite
fails.
- **Neither updater path ever swept the helper before the installer
ran.** Only the app-quit sweep (`state.shutdown_vss_helper()` in
`run_on_exit`) shuts the broker down. So:
- The **dev-channel silent** path (`silent_install_dev_update`, from the
periodic task) and the **manual** `install_update` path both called
`download_and_install` with the broker still alive.
- Because `/R` force-restarts the app **without** going through
`run_on_exit`, the pre-fix update paths also **seeded the orphan**: the
app restarted and left the elevated helper running, so the *next* update
tripped over the "helper from a previous app session" the QA note
describes. (This is why the QA repro shows a previous-session helper,
not just a same-session one.)
## Fix
**1. App-side (primary).** Both updater paths now call
`AppState::shutdown_vss_helper_for_update()` *before*
`download_and_install`. Unlike the app-quit sweep (a bare `shutdown()`),
it uses `set_enabled(false)` - a **superset** that:
- performs the same Shutdown+reap, including abandoning + reaping a
`Pending` launch per the #113 generation semantics, and
- **disables** the manager so a still-running sync that hits a locked
file cannot **re-launch** the elevated broker (re-locking the exe)
during the potentially-long download window.
On a **failed** install (app keeps running) the helper is **re-armed**
(`rearm_vss_helper_after_failed_update`, a lazy re-enable - no forced
UAC) so locked-file backup is not left silently degraded. On success the
app restarts, so a fresh process rebuilds the manager.
**2. Installer-side (belt-and-braces).** `NSIS_HOOK_PREINSTALL`
(`src-tauri/installer-hooks.nsh`, wired via
`bundle.windows.nsis.installerHooks`) `taskkill /F /IM
driven-vss-helper.exe`, error-tolerated, before any file is copied.
**3. Tests.** `update_sweep_reaps_ready_helper_and_blocks_relaunch`
(Ready broker -> reaped + disabled + no relaunch) and
`shutdown_vss_helper_for_update_disables_then_rearm_restores` (AppState
seam: disable then re-arm), using the injected launcher/manager doubles
from #112/#113. The *ordering* of the sweep relative to
`download_and_install` is verified by placement + review (the real
`Update`/download cannot be faked in a unit test); the NSIS hook is
CI-verified by the dev-channel/release bundle builds.
## Scope / findings to note
- **NSIS taskkill has a real limitation (documented in the .nsh).** The
helper always runs **elevated** (it refuses to start un-elevated), and
the default NSIS `installMode` is `currentUser`, so the updater-spawned
installer runs **un-elevated**. A medium-integrity `taskkill /F` cannot
terminate the high-integrity helper (access-denied), so the hook only
bites for an elevated/`perMachine`/admin-run install. The **effective**
same-session fix is the app-side pipe Shutdown; the durable fix for a
**crash-orphaned** elevated helper (app killed without any sweep) is a
**parent-death watchdog in the helper** (it has none today - it only
exits on an explicit pipe `Shutdown`). Recommend filing that as a
follow-up.
- **MSI target unaffected / no equivalent needed.** The Windows
auto-updater installs via the **NSIS** artifact (`*-setup.exe`), not the
`.msi` (`generate-update-json.mjs` prefers the `.exe`, matching
tauri-action's default). The `.msi` is only for manual installs, where
Windows Installer's Restart Manager already handles in-use files. So no
MSI-side change.
- **Pre-existing comment inaccuracy (left out of scope).**
`silent_install_dev_update`'s comment says it "does NOT force a restart
- the staged update applies on the next Driven restart", but NSIS
Passive = `/P /R` = restart. The dev channel force-restarts on every
update. Flagging as a follow-up; behavior unchanged here.
## Gates
`cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D
warnings`, `cargo test --workspace` all green. No UI or workflow files
touched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01LQMbCLUj5JT2e35qQMvsyA
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments