shell hardening: log() guards stdout (EPIPE) + uncaughtException log-and-continue

Exposed by the update drill: a packaged launch whose stdout consumer
exits (piped run, launcher) died on its next log line with an uncaught
EPIPE dialog. The stdout write is now guarded; the durable channel is
userData/logs/main.log. Stray main-process exceptions log and continue -
the window + child server are the product.
This commit is contained in:
amosblomqvist
2026-10-10 14:20:11 +03:00
parent e8e00a5237
commit 0af005d343
+20 -1
View File
@@ -80,6 +80,17 @@ if (!app.requestSingleInstanceLock()) {
let serverProcess = null;
// A GUI shell must never die to a stray exception (EPIPE dialogs, a race
// in an event handler): the window + child server are the product, and
// anything main-side is recoverable by logging it. Log-and-continue.
process.on("uncaughtException", (err) => {
try {
log(`uncaught: ${err?.stack ?? err}`);
} catch {
/* nothing left to write to */
}
});
/** A free TCP port (listen(0) → the kernel assigns one). The close→spawn
* race is theoretical; localhost ports are rarely contended, and a failed
* spawn surfaces in the health check with the child's stderr in the log. */
@@ -103,7 +114,15 @@ function freePort() {
function log(line) {
const stamp = new Date().toISOString();
console.log(`[lh ${stamp}] ${line}`);
// The stdout write is guarded (EPIPE hardening, 2026-10-10): a packaged
// app launched with a consumer-owned pipe (a launcher that exits, a
// piped run) must not die on its next log line when the pipe breaks.
// The file below stays the durable channel.
try {
console.log(`[lh ${stamp}] ${line}`);
} catch {
/* stdout gone — userData/logs/main.log still carries it */
}
// Belt and braces: also append to userData/logs so a misbehaving child
// can be diagnosed after the window is gone. Best effort only.
try {