Clicking Ignore skips replacing the exe, so the app silently stays on the old version and the same update is offered again next time.
Immediate workaround: click Retry instead of Ignore. By then the old process has exited and the write succeeds.
Analysis
install_update (src-tauri/src/lib.rs) calls update.download_and_install(...). On Windows, tauri-plugin-updater 2.10.1 spawns the NSIS installer with /P /R (passive + relaunch), runs the on_before_exit hook, then std::process::exit(0) (updater.rs ~L838-865). The installer therefore starts while the old cinny.exe is still alive and immediately tries to overwrite it; if the process (WebView2 teardown, tray, single-instance) hasn't fully released the file yet, NSIS hits the lock. Our app.restart() after the install call is never reached on Windows (the plugin has already exited), so it isn't the cause, but it is dead code on that platform.
Candidate fixes (need a Windows build to verify)
Register an on_before_exit hook via app.updater_builder().on_before_exit(...) that closes all webview windows and removes the tray icon so the exe handle is released before NSIS runs, and check whether the bundled NSIS template's running-app check (nsis_tauri_utils::FindProcess / kill in update mode) is actually active for /P passive installs in this Tauri version.
If that isn't enough: update.download() to a temp file ourselves and launch the installer detached via cmd /c "timeout /t 2 >nul && start "" "<installer>" /P /R" before app.exit(0), giving the process time to die. Uglier, but deterministic.
Alternatively switch plugins.updater.windows.installMode to quiet (/S /R) and confirm whether the silent path force-closes the running app (Tauri's template behaves differently in silent vs passive mode).
Related: the "update available" state losing on Settings close is fixed in cinny (useTauriUpdater now keeps it in a shared atom).
**Symptom (Windows, last two auto-updates):** the NSIS installer window pops up with
> Error opening file for writing: `C:\Users\<user>\AppData\Local\Lotus Chat\cinny.exe` — Abort / Retry / Ignore
Clicking **Ignore** skips replacing the exe, so the app silently stays on the old version and the same update is offered again next time.
**Immediate workaround:** click **Retry** instead of Ignore. By then the old process has exited and the write succeeds.
### Analysis
`install_update` (src-tauri/src/lib.rs) calls `update.download_and_install(...)`. On Windows, `tauri-plugin-updater` 2.10.1 spawns the NSIS installer with `/P /R` (passive + relaunch), runs the `on_before_exit` hook, then `std::process::exit(0)` (updater.rs ~L838-865). The installer therefore starts *while the old `cinny.exe` is still alive* and immediately tries to overwrite it; if the process (WebView2 teardown, tray, single-instance) hasn't fully released the file yet, NSIS hits the lock. Our `app.restart()` after the install call is never reached on Windows (the plugin has already exited), so it isn't the cause, but it is dead code on that platform.
### Candidate fixes (need a Windows build to verify)
1. Register an `on_before_exit` hook via `app.updater_builder().on_before_exit(...)` that closes all webview windows and removes the tray icon so the exe handle is released before NSIS runs, and check whether the bundled NSIS template's running-app check (`nsis_tauri_utils::FindProcess` / kill in update mode) is actually active for `/P` passive installs in this Tauri version.
2. If that isn't enough: `update.download()` to a temp file ourselves and launch the installer detached via `cmd /c "timeout /t 2 >nul && start "" "<installer>" /P /R"` before `app.exit(0)`, giving the process time to die. Uglier, but deterministic.
3. Alternatively switch `plugins.updater.windows.installMode` to `quiet` (`/S /R`) and confirm whether the silent path force-closes the running app (Tauri's template behaves differently in silent vs passive mode).
Related: the "update available" state losing on Settings close is fixed in cinny (`useTauriUpdater` now keeps it in a shared atom).
jared
self-assigned this 2026-09-15 08:47:06 -04:00
Fix shipped in 4648c8c: an NSIS pre-install hook that, on /UPDATE only, polls until cinny.exe can actually be opened for writing (every 250 ms, up to 15 s) before the template copies files.
Tested under Wine with a real running exe:
without the hook the copy fails (the bug);
with it, the installer waits out the remaining ~2.8 s, then copies;
a manual install (no /UPDATE) doesn't wait;
a stuck process hits the 15 s cap and falls through to the template's own running-app handling.
build-windows compiled it (run 2057). Leaving open until a real auto-update onto a build with this hook completes without the "Error opening file for writing" dialog. The installer that runs is the new version's, so the first update onto this build is already the test.
Fix shipped in `4648c8c`: an NSIS pre-install hook that, **on `/UPDATE` only**, polls until `cinny.exe` can actually be opened for writing (every 250 ms, up to 15 s) before the template copies files.
Tested under Wine with a real running exe:
- without the hook the copy fails (the bug);
- with it, the installer waits out the remaining ~2.8 s, then copies;
- a manual install (no `/UPDATE`) doesn't wait;
- a stuck process hits the 15 s cap and falls through to the template's own running-app handling.
`build-windows` compiled it (run 2057). **Leaving open until a real auto-update onto a build with this hook completes without the "Error opening file for writing" dialog.** The installer that runs is the *new* version's, so the first update onto this build is already the test.
The fix is in every build users update to. The update feed (releases/download/latest/release.json) serves 4.12.347 (published 2026-09-29 00:35 UTC). installerHooks: ./nsis/hooks.nsh has been in tauri.conf.json on main since 4648c8c, and every build since 23 Sep comes from there. The installer that runs during an update is the new version's, so every update since then has gone through the hook.
Review of the hook against the current upstream template and plugin:
Flag:tauri-plugin-updater 2.10.1 passes /UPDATE to the installer (updater.rs:812), so the hook's ${GetOptions} $CMDLINE "/UPDATE" gate fires on auto-updates only.
Order: in Tauri's installer.nsi, Section Install runs NSIS_HOOK_PREINSTALL (our wait) → CheckIfAppIsRunning (the template's kill) → File (the copy). So the wait happens before the copy, and a process stuck past 15 s still gets the template's kill.
Probe:FileOpen … a on an existing file opens it for writing without truncating it, and it's closed straight away, so the exe isn't touched.
Leftover edge case: if the old process hangs for more than 15 s, the template kills it and sleeps only 500 ms before copying, so the original race could still happen there. That's rare (a hung process); Retry still works in that case.
Still needed: one real Windows auto-update between two builds from after 23 Sep, with no "Error opening file for writing" dialog. If nobody has seen the dialog since, this can be closed.
## Re-check (2026-09-28)
**The fix is in every build users update to.** The update feed (`releases/download/latest/release.json`) serves **4.12.347** (published 2026-09-29 00:35 UTC). `installerHooks: ./nsis/hooks.nsh` has been in `tauri.conf.json` on `main` since `4648c8c`, and every build since 23 Sep comes from there. The installer that runs during an update is the new version's, so every update since then has gone through the hook.
**Review of the hook against the current upstream template and plugin:**
- **Flag:** `tauri-plugin-updater` 2.10.1 passes `/UPDATE` to the installer (`updater.rs:812`), so the hook's `${GetOptions} $CMDLINE "/UPDATE"` gate fires on auto-updates only.
- **Order:** in Tauri's `installer.nsi`, `Section Install` runs `NSIS_HOOK_PREINSTALL` (our wait) → `CheckIfAppIsRunning` (the template's kill) → `File` (the copy). So the wait happens before the copy, and a process stuck past 15 s still gets the template's kill.
- **Probe:** `FileOpen … a` on an existing file opens it for writing without truncating it, and it's closed straight away, so the exe isn't touched.
- **Leftover edge case:** if the old process hangs for more than 15 s, the template kills it and sleeps only 500 ms before copying, so the original race could still happen there. That's rare (a hung process); Retry still works in that case.
**Still needed:** one real Windows auto-update between two builds from after 23 Sep, with **no** "Error opening file for writing" dialog. If nobody has seen the dialog since, this can be closed.
Confirmed fixed on a real Windows machine (2026-10-21, windows runner, run 2797).
The new smoke step does what the auto-updater does. With the app running, it starts the installer in passive update mode (/P /UPDATE), and the app only goes away 3 s later.
Result: the installer waited, then finished by itself. Exit code 0, no "Error opening file for writing" dialog, and cinny.exe rewritten.
The step now runs with every Windows smoke run (PR #42), so a regression would show up there. Closing.
**Confirmed fixed on a real Windows machine** (2026-10-21, `windows` runner, run 2797).
The new smoke step does what the auto-updater does. With the app running, it starts the installer in passive update mode (`/P /UPDATE`), and the app only goes away 3 s later.
Result: the installer waited, then finished by itself. Exit code 0, **no "Error opening file for writing" dialog**, and `cinny.exe` rewritten.
The step now runs with every Windows smoke run (PR #42), so a regression would show up there. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom (Windows, last two auto-updates): the NSIS installer window pops up with
Clicking Ignore skips replacing the exe, so the app silently stays on the old version and the same update is offered again next time.
Immediate workaround: click Retry instead of Ignore. By then the old process has exited and the write succeeds.
Analysis
install_update(src-tauri/src/lib.rs) callsupdate.download_and_install(...). On Windows,tauri-plugin-updater2.10.1 spawns the NSIS installer with/P /R(passive + relaunch), runs theon_before_exithook, thenstd::process::exit(0)(updater.rs ~L838-865). The installer therefore starts while the oldcinny.exeis still alive and immediately tries to overwrite it; if the process (WebView2 teardown, tray, single-instance) hasn't fully released the file yet, NSIS hits the lock. Ourapp.restart()after the install call is never reached on Windows (the plugin has already exited), so it isn't the cause, but it is dead code on that platform.Candidate fixes (need a Windows build to verify)
on_before_exithook viaapp.updater_builder().on_before_exit(...)that closes all webview windows and removes the tray icon so the exe handle is released before NSIS runs, and check whether the bundled NSIS template's running-app check (nsis_tauri_utils::FindProcess/ kill in update mode) is actually active for/Ppassive installs in this Tauri version.update.download()to a temp file ourselves and launch the installer detached viacmd /c "timeout /t 2 >nul && start "" "<installer>" /P /R"beforeapp.exit(0), giving the process time to die. Uglier, but deterministic.plugins.updater.windows.installModetoquiet(/S /R) and confirm whether the silent path force-closes the running app (Tauri's template behaves differently in silent vs passive mode).Related: the "update available" state losing on Settings close is fixed in cinny (
useTauriUpdaternow keeps it in a shared atom).Fix shipped in
4648c8c: an NSIS pre-install hook that, on/UPDATEonly, polls untilcinny.execan actually be opened for writing (every 250 ms, up to 15 s) before the template copies files.Tested under Wine with a real running exe:
/UPDATE) doesn't wait;build-windowscompiled it (run 2057). Leaving open until a real auto-update onto a build with this hook completes without the "Error opening file for writing" dialog. The installer that runs is the new version's, so the first update onto this build is already the test.Re-check (2026-09-28)
The fix is in every build users update to. The update feed (
releases/download/latest/release.json) serves 4.12.347 (published 2026-09-29 00:35 UTC).installerHooks: ./nsis/hooks.nshhas been intauri.conf.jsononmainsince4648c8c, and every build since 23 Sep comes from there. The installer that runs during an update is the new version's, so every update since then has gone through the hook.Review of the hook against the current upstream template and plugin:
tauri-plugin-updater2.10.1 passes/UPDATEto the installer (updater.rs:812), so the hook's${GetOptions} $CMDLINE "/UPDATE"gate fires on auto-updates only.installer.nsi,Section InstallrunsNSIS_HOOK_PREINSTALL(our wait) →CheckIfAppIsRunning(the template's kill) →File(the copy). So the wait happens before the copy, and a process stuck past 15 s still gets the template's kill.FileOpen … aon an existing file opens it for writing without truncating it, and it's closed straight away, so the exe isn't touched.Still needed: one real Windows auto-update between two builds from after 23 Sep, with no "Error opening file for writing" dialog. If nobody has seen the dialog since, this can be closed.
Confirmed fixed on a real Windows machine (2026-10-21,
windowsrunner, run 2797).The new smoke step does what the auto-updater does. With the app running, it starts the installer in passive update mode (
/P /UPDATE), and the app only goes away 3 s later.Result: the installer waited, then finished by itself. Exit code 0, no "Error opening file for writing" dialog, and
cinny.exerewritten.The step now runs with every Windows smoke run (PR #42), so a regression would show up there. Closing.