fix(update): the relaunched OpsLog was starting invisibly

The relaunch after an update called hideConsole on the command that
starts the new build. That sets SysProcAttr{HideWindow: true}, which on
Windows becomes SW_HIDE in the STARTUPINFO handed to CreateProcess — and
Windows applies it to the first top-level window the new process shows.
So the updated OpsLog started correctly, took the single-instance mutex,
opened the logbook and connected the rig, and never appeared.

That is the report, in full: a process in the task manager, no window,
no autostart programs, and ending it then launching OpsLog by hand
working every time. Two operators, both on 0.27.23.

Why 0.27.19 did not fix it: that commit fixed the other half of the same
symptom — the new instance being less patient than the old one is slow —
which was real and is still fixed. The window was never part of it.

Where it came from: removing the PowerShell helper. Start-Process
launched the exe with a normal show; the direct exec.Command that
replaced it borrowed hideConsole from the console tools next to it,
where hiding a console window is exactly right and where the three
remaining callers (tasklist, taskkill, the deferred-swap PowerShell)
still belong.

The proof it was this and not the waiting: OpsLog relaunches itself in
two places, and RestartApp — the database switch — was byte-identical
except that it never called hideConsole. It has never been reported
broken. Both now go through relaunchCmd, so the rule lives in one place
with the reason written down, rather than in two call sites that differed
by one line.

Two tests: relaunchCmd leaves SysProcAttr nil, and update.go does not
call hideConsole at all.
This commit is contained in:
2026-09-10 15:40:02 +02:00
parent 7e6e1335e3
commit a8e870e098
5 changed files with 110 additions and 15 deletions
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// A relaunch of OpsLog itself must not suppress the new process's window.
//
// SysProcAttr is where that damage was done: hideConsole sets HideWindow, which
// on Windows becomes SW_HIDE in the STARTUPINFO, and Windows applies it to the
// first top-level window the new process shows. The updated OpsLog started,
// took the single-instance mutex and connected the rig — invisibly. Two
// operators on 0.27.23 reported it as "it goes to reload and just fails to
// load": a process in the task manager, no window, and killing it then starting
// it by hand working every time.
//
// Nil, not "some specific value": there is nothing a self-relaunch needs from
// STARTUPINFO, and anything set there is a window flag waiting to be wrong.
func TestRelaunchCmdDoesNotTouchTheWindow(t *testing.T) {
cmd := relaunchCmd(filepath.Join("C:", "OpsLog", "OpsLog.exe"), "--post-update", "--wait-pid", "1234")
if cmd.SysProcAttr != nil {
t.Errorf("relaunchCmd set SysProcAttr = %+v; a self-relaunch must leave the window alone", cmd.SysProcAttr)
}
if len(cmd.Args) != 4 || cmd.Args[1] != "--post-update" || cmd.Args[3] != "1234" {
t.Errorf("args = %v, want the exe plus the three passed through", cmd.Args)
}
// The working directory matters: the new instance keeps its data folder
// beside the executable, and inheriting the old process's cwd would look for
// it somewhere else entirely.
if cmd.Dir != filepath.Join("C:", "OpsLog") {
t.Errorf("Dir = %q, want the executable's folder", cmd.Dir)
}
}
// hideConsole is right for the console tools and wrong for OpsLog, and the two
// live a few lines apart. This is the guard that stops the update path
// borrowing it again — which is how it broke the first time, when the
// PowerShell helper was replaced by a direct exec.Command beside them.
func TestUpdateRelaunchDoesNotHideTheWindow(t *testing.T) {
src, err := os.ReadFile("update.go")
if err != nil {
t.Fatalf("read update.go: %v", err)
}
if strings.Contains(string(src), "hideConsole(") {
t.Error("update.go calls hideConsole — a relaunch of OpsLog must not hide its window (see relaunch.go)")
}
if !strings.Contains(string(src), "relaunchCmd(exe,") {
t.Error("update.go no longer relaunches through relaunchCmd, where that rule is written down")
}
}