Review feedback on the #82328/#82366 hand-off, all four points plus the
missing progress GUI:
1. FAIL CLOSED. Both preflight gates aborted-open: a Desktop still alive
after 30s proceeded anyway, and a shim locked after 20s proceeded
with --force - both mutate a potentially locked install (the exact
Access-denied brick class). Now: desktop-alive -> exit 4, nothing
changed; shim-locked -> exit 5, nothing changed. Both relaunch the
Desktop so the user is never stranded.
2. TRUTHFUL COMPLETION. `hermes update` treats a Desktop GUI build
failure as non-fatal (warns, exits 0) - correct for CLI use, a lie
for a Desktop-driven update that then relaunches the OLD exe as
"success". The script now detects the warning in the update output,
retries the build once (`hermes desktop --force-build --build-only`),
and exits 6 with an honest message when it still fails.
3. MARKER OWNERSHIP. Cleanup now removes the marker only while OUR pid
still owns it - a handoff partner that rewrote the marker keeps its
claim (same rule as UpdateLock.release).
4. RESULT SURFACING. The script writes .hermes-update-result.json on
every exit path (ok, exit_code, message, branch, finished_at). New
electron/handoff-result.ts consumes it exactly once at the boot
update-gate: success logs, failure shows a real dialog pointing at
desktop-update-handoff.log. Stale (>30min) and malformed results are
consumed silently. Previously a failed detached update was
indistinguishable from "nothing happened" - the exact live report
that triggered this work.
5. PROGRESS UI. The old Tauri updater showed a window; the script ran
in a hidden console with zero feedback. It now shows a WinForms
progress window (marquee bar + streaming log) pumped via DoEvents
during the update; -NoUi keeps tests/headless sessions clean, and a
WinForms-unavailable session degrades to log-only.
Also: subprocess execution moved from Start-Process (ExitCode
unreliably $null under PS 5.1 even with the Handle workaround -
observed live: happy path reported "failed (exit )") to
System.Diagnostics.Process with synchronous stdout pumping, which
keeps the UI alive and the exit code real.
E2E on a real Windows box, sandbox HERMES_HOME + compiled fake
hermes.exe, all five paths:
- happy: exit 0, result {ok:true, "Update complete."}
- shim held open via O_RDWR: exit 5, nothing mutated, honest result
- desktop pid alive (60s ping child): exit 4 after the 30s gate
- update exits 0 printing "Desktop build failed" + rebuild fails:
exit 6, result names the stale build and the retry command
- foreign-owned marker: overwritten by step-0 claim, removed as owner;
ownership check verified in the cleanup path
vitest 18/18 (5 new handoff-result tests), typecheck 3 projects clean,
eslint clean, PS 5.1 parse + footguns + ASCII-only clean.
Remaining known gap (deliberate): the full click-to-relaunch lifecycle
through a REAL Desktop build still needs one live Windows verification
after this lands - tracked in the PR body.