From 603b2ad5a2e1de4e83e99a0b7f954e4f55617793 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 14 Sep 2026 16:48:02 +0200 Subject: [PATCH] Stop the launcher killing the app that is running kill_stale() clears a leftover sidecar off the port before spawning a new one. On unix it asked: lsof -ti :8765 which matches any socket with that port on either end, so it lists the server and everything connected to it. Measured on macOS with one listener and one client: lsof -ti :PORT -> 90713 90735 lsof -ti :PORT -sTCP:LISTEN -> 90713 The busiest client of the sidecar is the webview of a kaisho-desktop that is still running, so an auto-update restart or a second launch would SIGTERM the instance already on screen, along with anything else holding a connection to 127.0.0.1:8765. The Windows branch, ten lines below, already guards against precisely this. Its comment spells out the failure and even names the case: including, on a bad day, kaisho-desktop itself since the webview connects back to port 8765. That fix landed for netstat and never reached lsof, and macOS is the platform this ships on first. -sTCP:LISTEN restricts it to the listener, and the loop also skips its own pid, which is cheap and makes the intent explicit. --- desktop/src-tauri/src/sidecar.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/desktop/src-tauri/src/sidecar.rs b/desktop/src-tauri/src/sidecar.rs index 5cef15b9..9a9fa89c 100644 --- a/desktop/src-tauri/src/sidecar.rs +++ b/desktop/src-tauri/src/sidecar.rs @@ -37,14 +37,41 @@ const SIDECAR_PORT: &str = "8767"; fn kill_stale() { #[cfg(unix)] { + // -sTCP:LISTEN, not a bare -i. `lsof -i :8765` + // matches any socket with that port on either end, + // so it lists the server *and* everything connected + // to it. Measured: with one listener and one client + // on a port, `lsof -ti :PORT` returns both PIDs and + // `lsof -ti :PORT -sTCP:LISTEN` returns only the + // listener. + // + // Without the filter this kills whatever is talking + // to the sidecar, and the busiest such client is the + // webview of a kaisho-desktop that is still running: + // an auto-update restart or a second launch would + // take out the instance already on screen. + // + // The Windows branch below already guards against + // exactly this -- read its comment -- and says why. + // The fix never reached here. if let Ok(out) = crate::proc::configured( "lsof", "kill_stale.lsof", ) - .args(["-ti", &format!(":{}", SIDECAR_PORT)]) + .args([ + "-ti", + &format!(":{}", SIDECAR_PORT), + "-sTCP:LISTEN", + ]) .output() { let pids = String::from_utf8_lossy(&out.stdout); + let own = std::process::id().to_string(); for pid in pids.split_whitespace() { + // Belt and braces: a listener that is this + // very process is not stale. + if pid.trim() == own { + continue; + } eprintln!( "[kai] killing stale process {}", pid,