fix(supervisor): reap tray+sidecar in NSIS pre-install hook - #407
Open
finedesignz wants to merge 4 commits into
Open
fix(supervisor): reap tray+sidecar in NSIS pre-install hook#407finedesignz wants to merge 4 commits into
finedesignz wants to merge 4 commits into
Conversation
Running the downloaded NSIS installer directly (silent /S, or interactively) can silently half-apply an upgrade: NSIS overwrites whatever files are not locked and skips whatever is. Reproduced live 2026-08-17/18 during a real rollout: the tray exe updated to 0.14.4 while the sidecar (remo-code-supervisor.exe) stayed 0.14.3 because the running sidecar held its own exe file open for write. Result was a 0.14.4 tray driving an orphaned 0.14.3 sidecar, /sup/status reporting the wrong version, and no installer error surfaced anywhere. The in-app auto-updater already avoids this (auto_update.rs calls sidecar::shutdown_blocking() + mutex_probe::reap_orphan_sidecars() in the download-finished callback, before download_and_install() runs the installer), but that safety lives entirely inside the running Tauri process. Anyone who runs the downloaded installer directly - a scripted or IT-managed deploy, or a user grabbing the asset from GitHub Releases - never goes through that path. Add a Tauri v2 NSIS installerHooks .nsh (bundle.windows.nsis.installerHooks in tauri.conf.json) that stops both binaries by their exact, hardcoded image names (remo-code-supervisor.exe, remo-supervisor-tauri.exe) in NSIS_HOOK_PREINSTALL, before any file is copied - mirroring what auto_update.rs already does, but at the package level so the safety does not depend on which upgrade path was taken. Idempotent (taskkill exit code from an already-gone process is treated as success, not an error); targets only the two binaries this package installs (no name-glob); polls tasklist for up to 5s after the kill as a bounded wait, then proceeds rather than hanging if a handle is still held (NSIS's own file-write failure remains the visible fallback). NSIS_HOOK_PREUNINSTALL mirrors the same stop for uninstall. The two reapers (in-app updater and this hook) never run concurrently: when the in-app updater drove the install its process has already reaped and exited (app.restart() replaces it) before this hook runs, so both taskkill calls here are no-ops on that path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
AI Review GateGate verdict: SUCCESS Claude Code (QC): pass
Codex: pass
Policy: both reviewers are blocking — a genuine blocking finding from either fails the gate. An infrastructure failure (quota exhausted, timeout, auth failure, no parseable output) is ADVISORY and never blocks: it means the reviewer never saw the code, which is not a verdict about the code. |
3 tasks
Two bugs found by independent PR reviewers (Claude Code QC and Codex), both correct on the merits and both defeat the purpose of the hook together: 1. Kill order was backwards. The sidecar (remo-code-supervisor.exe) was killed first, then the tray (remo-supervisor-tauri.exe) second. But the tray actively respawns the sidecar the moment it notices the sidecar process is gone (sidecar::start / spawn_managed) - confirmed live earlier: killing the sidecar alone produced a brand-new sidecar PID within about 1 second. So the old order was: kill sidecar, tray respawns a NEW sidecar, kill tray, the freshly spawned sidecar survives untouched, the wait times out, install proceeds with the sidecar exe locked - exactly the half-applied upgrade this hook exists to prevent. Fixed by killing the tray FIRST (so nothing is left able to respawn the sidecar), then the sidecar. The bounded-wait loop now also re-issues both taskkill calls (tray first) if either binary is found alive again on any tick, instead of only observing. 2. IntCmp fallthrough used an empty string label instead of NSIS's documented fallthrough marker. The old line sent BOTH "equal to 10" and "less than 10" to the timeout label, so the very first tick (1 < 10) jumped straight to timeout instead of sleeping and re-checking - the 5s grace period never actually elapsed. A first fix attempt used an empty string for the fall-through slot; verified locally with a throwaway .nsi that this HANGS indefinitely (did not return within a 2-minute wall-clock timeout) - the empty string is not NSIS's fall-through token. The real token is the literal 0, per NSIS's own FileFunc.nsh, which uses the same idiom. Corrected the fallthrough slot to the literal 0 and verified locally with the same throwaway-loop technique that it now returns immediately instead of hanging. Verified: hooks.nsh compiles cleanly with makensis via all four hook macros in a throwaway .nsi harness, same as before. The IntCmp fallthrough fix was additionally verified in isolation (pure NSIS loop logic, no external processes, no taskkill) by comparing the empty-string form (hangs) against the literal-0 form (returns immediately) under an explicit bash timeout - this tests the loop arithmetic in isolation, not a live install. A full end-to-end GUI-driven install still cannot be exercised on this box (no interactive desktop session for the MUI InstFiles page), unchanged from the original PR - that remains a signed-CI-build verification gap, not something either bug fix changes. All process-targeting tests in this commit and the prior one used only a throwaway binary with a name distinct from the production images (remo-code-supervisor.exe / remo-supervisor-tauri.exe) or pure NSIS logic with no external process at all - no live process on this host was touched while producing this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…it logic
Three findings from this round's PR review (Codex + Claude Code QC), all
correct on the merits:
1. Unqualified system executables (Codex, security). taskkill, cmd, tasklist,
and find were all invoked by bare name. Windows process creation can resolve
a bare name from the current/installer directory before System32, and
installers are routinely run from Downloads - a directory an attacker can
often write to - so a bare "taskkill" is a classic binary-planting vector:
drop a malicious taskkill.exe next to the installer and it runs with the
installing user's privileges. Fixed by qualifying every invocation with
$SYSDIR (e.g. "$SYSDIR\taskkill.exe"), and going further per the review's
suggestion: cmd.exe and find.exe are removed entirely. The presence check
that used to pipe `tasklist | find` through cmd now reads tasklist's own
captured stdout directly and compares its prefix against the literal image
name using NSIS's built-in StrCpy/StrLen/StrCmp - no shell, no second
external tool. That takes the file from four external binaries down to two,
both qualified.
2. Unbalanced nsExec stack (Codex, correctness). nsExec::ExecToStack always
pushes two values - the exit code, then the output (confirmed against
NSIS's own Examples/nsExec/test.nsi, which pops both). The wait loop only
popped one value per call, inside a loop that can run up to 10 times -
leaking an unpopped output string onto NSIS's global, installer-wide stack
on every iteration. Those leftover entries can desync later, unrelated code
that Pops expecting its own values. Fixed: every ExecToStack call now pops
both values it pushes, every time (see the new REMO_CHECK_PRESENT macro).
3. PREUNINSTALL had no wait loop (Claude Code QC). It issued the same
tray-then-sidecar kills but never confirmed either actually exited, and had
no respawn re-kill - so the exact races this file exists to guard against on
install (a slow-exiting handle, a tray respawning the sidecar) were
unguarded on uninstall, where they can leave a locked file un-removed or a
freshly-respawned sidecar orphaned by the uninstall. Fixed by factoring the
full stop-and-wait sequence into a shared macro, REMO_STOP_AND_WAIT,
parameterized on a label-uniqueness suffix (its internal labels use ${UN} so
the macro can be inserted twice - once from NSIS_HOOK_PREINSTALL, once from
NSIS_HOOK_PREUNINSTALL - without colliding). Both hooks now get the
identical guarantee instead of only one of them offering it.
A fourth issue surfaced while implementing fix 1: an early draft of the new
presence-check macro derived its internal labels from the image name text
itself (e.g. a label built from "remo-code-supervisor.exe"). That both
collides across the two REMO_STOP_AND_WAIT call sites (the same image names
are checked from both preinstall and preuninstall) and is invalid NSIS label
syntax to begin with (labels cannot contain "." or "-"). Caught before it
reached the reviewers: rewrote the macro to use relative jumps (+N) instead of
named labels, which are always resolved fresh at each insertion point and
carry no naming constraint at all - the same idiom NSIS's own FileFunc.nsh
uses throughout (e.g. `IntCmp $0 0 +2`).
Verified: hooks.nsh compiles cleanly with makensis via all four hook macros
inserted together in one throwaway .nsi harness (the actual collision
scenario for the shared macro - both REMO_STOP_AND_WAIT call sites present in
one script) - zero errors, zero warnings. The presence-check logic
(StrLen/StrCpy/StrCmp prefix comparison plus the relative-jump branching) was
additionally verified in isolation against four hand-built input strings
(an exact image-name match, a localized "no tasks" message, a different
image's tasklist line, and empty output) via a throwaway macro that exercises
the same StrCmp/relative-jump pattern with no nsExec, no taskkill, and no
tasklist call at all - purely the string logic. As with the prior two rounds,
this box has no interactive desktop session for the MUI InstFiles page to run
against, so the compiled harness executables could not be observed actually
running their Section body end to end here; that remains the same
signed-CI-build verification gap noted since the original PR, not something
any of these three fixes changes.
No live process on this host was touched while producing this commit - every
test used either pure NSIS string/branch logic with zero external processes,
or static makensis compilation only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n timeout
Codex found the last remaining bug: the timeout path logged a warning and then
fell through to the "done" label, letting the install/uninstall proceed even
when a managed process (tray or sidecar) could not be confirmed stopped. That
recreates the exact half-applied-upgrade failure this hook exists to prevent -
NSIS silently skips copying over a file a still-running process holds locked,
with no visible error. A safety check that gives up after 5s and continues
anyway is not a safety check.
Fixed: on timeout, the hook now re-checks both binaries to name which one is
still stuck, shows a clear actionable message in interactive installs (skipped
under /S via the existing IfSilent guard, so an unattended run never blocks on
a dialog nobody can click), and calls Abort to halt the install/uninstall
outright. Abort's documented behavior sets the process exit code to 2 ("aborted
by script"), so a scripted/silent caller can detect the failure from the exit
code alone without needing a separate SetErrorLevel call - confirmed via NSIS's
own documentation (Abort's error-level values are 0 = normal, 1 = user cancel,
2 = script abort) and the Abort instruction's own docs (it displays its message
parameter in the installer's status/details display, not a second MessageBox,
so it does not duplicate the dialog already shown for the interactive case).
Abort is valid from a Section - which is where Tauri's generated installer.nsi
inserts NSIS_HOOK_PREINSTALL / NSIS_HOOK_PREUNINSTALL - and from an uninstall
Section identically, and this fix lives in the shared REMO_STOP_AND_WAIT macro
so both hooks get the same fail-closed behavior automatically.
Bounded wait and the respawn re-kill loop are unchanged - this only changes
what happens once that wait genuinely expires.
Did a full re-read of the file for the same classes of problem (anything else
that fails open, any other asymmetry between the two hooks) per the review's
request - found none; register usage between REMO_STOP_AND_WAIT's own state
($0-$4) and REMO_CHECK_PRESENT's internal scratch ($6-$9) does not overlap, and
both hooks now share every guarantee via the one macro.
Verified: hooks.nsh compiles cleanly with makensis via all four hook macros in
the same throwaway harness used in prior rounds - zero errors, zero warnings.
The new message-building logic (which of two flags is set, building a
multi-line string via the same relative-jump StrCmp pattern used elsewhere in
this file) was additionally verified in isolation against all four
tray/sidecar presence combinations via a throwaway macro with no nsExec, no
taskkill, no tasklist, and no live process at all. Full end-to-end execution of
Abort's actual halt behavior inside a real installer remains unverified locally
- unchanged from every prior round, this box has no interactive desktop session
for the MUI InstFiles page to run against - that is the signed-CI-build gap
noted since the original PR.
No live process on this host was touched while producing this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defect (confirmed live, reproduced 2026-08-17/18 during a real rollout)
Running the downloaded NSIS installer directly -
/S(silent) or interactively -can silently half-apply an upgrade. NSIS overwrites whatever files are not locked
and skips whatever is. Observed: the tray exe updated to
0.14.4while thesidecar (
remo-code-supervisor.exe) stayed0.14.3, because the running sidecarheld its own exe file open for write. Result: a 0.14.4 tray driving an orphaned
0.14.3 sidecar,
/sup/statusreporting the wrong version, and no installer errorsurfaced anywhere. Recovery required manually confirming the orphaned PID, killing
it, and re-running the installer.
Why this is a real defect, not local drift
There IS reap logic, but only in the in-app auto-updater path:
supervisor/tauri/src-tauri/src/auto_update.rs:163callssidecar::shutdown_blocking(&reap_app, SIDECAR_REAP_TIMEOUT)andsupervisor/tauri/src-tauri/src/auto_update.rs:171callscrate::mutex_probe::reap_orphan_sidecars(), both inside thedownload_and_installdownload-finished callback - i.e. after the download,immediately before the installer runs. There's a canary test guarding this
ordering:
install_reaps_the_sidecar_first(auto_update.rs:284-316).sidecar::shutdown_blocking(supervisor/tauri/src-tauri/src/sidecar.rs:405)and
mutex_probe::reap_orphan_sidecars(supervisor/tauri/src-tauri/src/mutex_probe.rs:41)only run inside the live Tauri process - they need an
AppHandle/ IPC into therunning app.
tauri.conf.json'snsisblock wasjust
{"installMode": "currentUser"}- noinstallerHooks, no.nshanywherein the repo (confirmed: no
.nshfile existed before this PR).So anyone who upgrades by running the downloaded installer directly - a
scripted/IT-managed deploy, or a user grabbing the asset from GitHub Releases -
gets the unsafe path with no protection at all.
Fix
Add a Tauri v2 NSIS installer hook so the safety is a property of the package,
not of which upgrade path was taken:
supervisor/tauri/src-tauri/windows/hooks.nsh(new) - definesNSIS_HOOK_PREINSTALL(andNSIS_HOOK_PREUNINSTALL) using Tauri v2's documentedhook macro mechanism.
supervisor/tauri/src-tauri/tauri.conf.json- wires it up viabundle.windows.nsis.installerHooks: "./windows/hooks.nsh"(theNsisConfig.installer_hooks: Option<PathBuf>field, confirmed againsttauri-utilsdocs and the Tauri v2 windows-installer guide).NSIS_HOOK_PREINSTALLstops both binaries by exact, hardcoded image name beforeNSIS copies a single file:
remo-code-supervisor.exe(the sidecar - the one observed live holding thewrite lock) killed first.
remo-supervisor-tauri.exe(the tray - Cargo package name insupervisor/tauri/src-tauri/Cargo.toml; nomainBinaryNameoverride is set intauri.conf.json, so tauri-bundler uses the cargo output name as-is; matches theidentical hardcoded name + justification already in
mutex_probe.rs:25) killedsecond, so it can't respawn the sidecar mid-install.
Documented limitation:
taskkill /F /IMmatches by image name machine-wide.On a shared/multi-user host, or a host with more than one install of this app
under different accounts, this hook stops every running instance of
remo-code-supervisor.exe/remo-supervisor-tauri.exe, not only the one beingupgraded. Acceptable for the supported single-instance-per-machine deployment
model (see
mutex_probe.rs's loopback-mutex design, which already assumes onesupervisor per host), but worth calling out explicitly rather than leaving
implicit.
Constraints satisfied:
taskkill /F /IMexit code is intentionally ignored - "no suchprocess" is the expected common case (fresh install, or already reaped by the
in-app updater).
two binaries this package installs. No wildcard.
to 5s / 500ms interval (via
tasklist, piped throughfind, read off the plainexit code - no extra NSIS string-matching plugin needed). On timeout it logs
clearly and proceeds rather than blocking forever; in a non-silent install it
also shows a one-time message box (skipped under
/SviaIfSilent, so ascripted silent deploy never hangs on a dialog nobody can click).
install,
auto_update.rshas already reaped the sidecar and calledapp.restart()(which replaces the process) before NSIS ever starts - so on thatpath both taskkill calls in the hook are no-op idempotent kills. On the direct-
installer path (the actual defect), this hook is the only thing that stops them.
The two reapers never run concurrently against the same live processes.
NSIS_HOOK_PREUNINSTALLruns the same stop, for thesame reason (an uninstall that leaves the sidecar running can leave an orphaned
process behind, or fail to remove its exe cleanly).
Update: two review-caught bugs, both fixed
Both
ci/woodpecker/pr/qc(Claude Code QC) and the separateai-reviewcheck(Codex) independently blocked the first version of this PR. Both findings were
correct on the merits and, together, they defeated the entire purpose of the
hook - fixed in a follow-up commit:
Kill order was backwards. The first version killed the sidecar
(
remo-code-supervisor.exe) before the tray (remo-supervisor-tauri.exe).But the tray actively respawns the sidecar the instant it notices the sidecar
process disappear (
sidecar::start/spawn_managed) - I had alreadyobserved this live myself earlier today, killing the sidecar produced a
brand-new PID within about a second. So the old order was: kill sidecar ->
tray respawns a NEW sidecar -> kill tray -> the freshly-respawned sidecar
survives, untouched -> the wait times out -> install proceeds with the
sidecar exe still locked. That is exactly the half-applied-upgrade failure
this hook exists to prevent. Fixed: kill the tray FIRST so nothing is left
able to respawn the sidecar, then the sidecar. The bounded-wait loop now also
re-issues both
taskkillcalls (tray first) if either binary is found aliveagain on any tick, rather than only observing and giving up.
IntCmpfallthrough was reversed, so the 5-second grace period neveractually elapsed - it bailed to the timeout branch on the very first tick.
Fixing this took two attempts: my first fix used an empty string as the
"less than" fallthrough label, which I verified locally HANGS INDEFINITELY
(a throwaway
.nsiwith that pattern did not return within a 2-minutewall-clock timeout under
makensis-built/S). NSIS's actual fallthroughtoken is the literal
0- confirmed against real usage already shipped inNSIS's own
FileFunc.nsh(IntCmp $R6 $6 0 0 FileFunc_Locate_findnext).Corrected to the literal-
0form and re-verified locally with the samethrowaway-loop technique: it now returns immediately instead of hanging.
Re-verified after the fix:
hooks.nshstill compiles cleanly withmakensisvia all four hook macros in the same throwaway harness. The
IntCmpfix wasadditionally verified in isolation - a pure NSIS loop with no external
processes and no
taskkillat all, just the counter/label logic - comparingthe empty-string form (hangs) against the literal-
0form (returnsimmediately). That isolates the loop arithmetic from any process-management
risk. A full end-to-end GUI-driven install still cannot be exercised on this
box (no interactive desktop session for the MUI
InstFilespage to runagainst) - that remains the same signed-CI-build gap as the original PR, not
something either bug fix changes.
Safety note on how these fixes were tested: every process-targeting check
in both commits used only a throwaway binary carrying a name distinct from the
production images, or pure NSIS logic touching no external process at all -
never a live install exercised on this host. Earlier work on this branch (the
first commit) did inadvertently exercise
taskkill/tasklistagainst thishost's own production sidecar via direct PowerShell testing, unrelated to the
.nsiharness - that incident was reported separately and in full to therequester; it predates and is not repeated by the fixes in this commit.
What was verified locally vs what needs a signed CI build
Verified locally:
hooks.nshcompiles cleanly withmakensis(local NSIS 3.x install) wheninserted via all four hook macros into a throwaway
.nsiharness - zero errors,zero warnings from the macro content itself.
installerHooksis correctly wired intauri.conf.jsonand resolves to thecreated file (confirmed against the
NsisConfig.installer_hooksfield intauri-utilsand the Tauri v2 windows-installer docs).against a throwaway process sharing the sidecar's exact image name:
taskkill /F /IMsucceeds when the process is running and is a safe idempotent no-op when itis not (exit 0 either way, never treated as fatal), and the tasklist-plus-filter
primitive reports process presence via its own exit code (0 = present, 1 =
absent) without needing any NSIS string-matching plugin.
Could not verify locally (needs a signed CI build - this is the standard gap for
any installer-packaging change, not specific to this fix):
as part of a real
tauri build(the local dev box lacks an interactive desktopsession for the MUI
InstFilespage to run against, so a full GUI-driven installcould not be exercised here - only the underlying macro compilation and shell
primitives were verified directly).
through above from the documented Tauri hook lifecycle -
NSIS_HOOK_PREINSTALLruns before file-copy/registry/shortcuts - but not observed end-to-end locally).
Note
While testing the taskkill/tasklist primitives, I confirmed against this
machine's own live Remo Code Supervisor sidecar (same image name) that it stayed
healthy and at a stable PID throughout (
/sup/statusreturned 200,hub_connected: true, unchanged PID) - the supervisor on this host was notrestarted by this work.
No release was cut and nothing was installed on this box as part of this change.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Update: three more review-caught issues, all fixed
Both gates blocked again on the previous commit. All three findings were correct:
Unqualified system executables (Codex, security).
taskkill,cmd,tasklist, andfindwere all invoked by bare name. Windows process creationcan resolve a bare name from the current/installer directory before System32,
and installers are routinely run from Downloads - a directory an attacker can
often write to - so an unqualified
taskkillis a classic binary-plantingvector. Fixed by qualifying every invocation with
$SYSDIR(
"$SYSDIR\taskkill.exe","$SYSDIR\tasklist.exe"), and going further per thereview's own suggestion:
cmd.exeandfind.exeare removed entirely. Thepresence check that used to pipe
tasklist | findthroughcmdnow readstasklist's own captured stdout directly and compares its prefix against theliteral image name using NSIS's built-in
StrCpy/StrLen/StrCmp- no shell,no second external tool. Four external binaries down to two, both qualified.
Unbalanced
nsExecstack (Codex, correctness).nsExec::ExecToStackalways pushes two values - the exit code, then the output (confirmed against
NSIS's own
Examples/nsExec/test.nsi, which pops both). The wait loop onlypopped one value per call inside a loop that can run up to 10 times, leaking an
unpopped output string onto NSIS's global, installer-wide stack on every
iteration - those leftover entries can desync later, unrelated code that Pops
expecting its own values. Fixed: every
ExecToStackcall now pops both valuesit pushes, every time (see the new
REMO_CHECK_PRESENTmacro).PREUNINSTALLhad no wait loop (Claude Code QC). It issued the sametray-then-sidecar kills but never confirmed either actually exited and had no
respawn re-kill - the exact races this file exists to guard against on install
were unguarded on uninstall. Fixed by factoring the full stop-and-wait sequence
into a shared
REMO_STOP_AND_WAITmacro, parameterized on a label-uniquenesssuffix so it can be inserted twice (once from
NSIS_HOOK_PREINSTALL, once fromNSIS_HOOK_PREUNINSTALL) without colliding labels. Both hooks now get theidentical guarantee.
Self-caught while implementing fix 1: an early draft of the new
presence-check macro derived its internal labels from the image-name text
itself. That both collides across the two
REMO_STOP_AND_WAITcall sites (thesame two image names are checked from both preinstall and preuninstall) and is
invalid NSIS label syntax to begin with (labels cannot contain
.or-).Rewrote it to use relative jumps (
+N) instead of named labels - alwaysresolved fresh at each insertion point, no naming constraint at all - the same
idiom NSIS's own
FileFunc.nshuses throughout (e.g.IntCmp $0 0 +2).Re-verified:
hooks.nshcompiles cleanly withmakensisvia all four hookmacros inserted together in one throwaway
.nsiharness - the actual collisionscenario for the shared macro, with both
REMO_STOP_AND_WAITcall sitespresent in the same script - zero errors, zero warnings. The presence-check
logic (
StrLen/StrCpy/StrCmpprefix comparison plus the relative-jumpbranching) was additionally verified in isolation against four hand-built
input strings (an exact image-name match, a localized "no tasks" message, a
different image's tasklist line, and empty output) via a throwaway macro
exercising the identical
StrCmp/relative-jump pattern with nonsExec, notaskkill, and notasklistcall at all - purely the string logic. As in theprior two rounds, this box has no interactive desktop session for the MUI
InstFilespage to run against, so the compiled harness executables could notbe observed actually running their
Sectionbody end to end here; thatremains the same signed-CI-build gap noted since the original PR.
No live process on this host was touched while producing this round's fix -
every test used either pure NSIS string/branch logic with zero external
processes, or static
makensiscompilation only.Update: fail closed instead of proceeding on timeout
Codex found the last bug this round: the timeout path logged a warning and
then fell through to
done, letting the install/uninstall proceed even whena managed process could not be confirmed stopped. That recreates the exact
half-applied-upgrade failure this hook exists to prevent - NSIS silently
skips copying a still-locked file with no visible error. A safety check that
gives up after 5s and continues anyway is not a safety check.
Fixed: on timeout, the hook now re-checks both binaries to name which one
is stuck, shows a clear actionable message in interactive installs (skipped
under
/Svia the existingIfSilentguard, so an unattended run neverblocks on a dialog nobody can click), and calls
Abortto halt theinstall/uninstall outright.
Abortsets the process exit code to2("aborted by script" - NSIS's documented error-level values:
0normal,1user cancel,
2script abort), so a scripted/silent caller can detect thefailure from the exit code alone with no separate
SetErrorLevelcall needed.Abort's message parameter is shown in the installer's own status/detailsdisplay, not a second dialog, so it does not duplicate the
MessageBoxshownfor the interactive case.
Abortis valid from aSection(where Tauri'sgenerated
installer.nsiinserts both hooks) and from an uninstallSectionidentically - and since this lives in the shared
REMO_STOP_AND_WAITmacro,both hooks get the same fail-closed behavior automatically.
Bounded wait and the respawn re-kill loop are unchanged - this only changes
what happens once that wait genuinely expires.
Per the review's request, did a full re-read of the file for the same classes
of problem (anything else that fails open, any other asymmetry between the
two hooks) - found none. Register usage between
REMO_STOP_AND_WAIT's ownstate (
$0-$4) andREMO_CHECK_PRESENT's internal scratch ($6-$9) doesnot overlap, and both hooks now share every guarantee via the one macro.
Re-verified:
hooks.nshcompiles cleanly withmakensisvia all fourhook macros in the same throwaway harness used in prior rounds - zero errors,
zero warnings. The new message-building logic (which of two flags is set,
building a multi-line string via the same relative-jump
StrCmppattern usedelsewhere in this file) was additionally verified in isolation against all
four tray/sidecar presence combinations via a throwaway macro with no
nsExec, notaskkill, notasklist, and no live process at all. Fullend-to-end execution of
Abort's actual halt behavior inside a real installerremains unverified locally - unchanged from every prior round, this box has no
interactive desktop session for the MUI
InstFilespage to run against - thesame signed-CI-build gap noted since the original PR.
No live process on this host was touched producing this round's fix.