From 92cd53041015e4d56eb8e05f3910ef2491771c6f Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 28 Jan 2026 09:34:03 -0800 Subject: [PATCH] fix(desktop): restore auto-restart of stale daemon in dev mode Partially reverts 8b5cbc42 to restore the mtime-based detection that automatically restarts the terminal daemon when the script is rebuilt in development mode. This fixes connection errors that occur when the daemon gets out of sync with the app after code changes. Keeps the pty-subprocess.ts fix from the original commit (waiting for tree-kill callback before exiting). --- .../src/main/lib/terminal-host/client.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/apps/desktop/src/main/lib/terminal-host/client.ts b/apps/desktop/src/main/lib/terminal-host/client.ts index 7994d8fc3c1..6bbbdc8cf3e 100644 --- a/apps/desktop/src/main/lib/terminal-host/client.ts +++ b/apps/desktop/src/main/lib/terminal-host/client.ts @@ -73,6 +73,7 @@ const SOCKET_PATH = join(SUPERSET_HOME_DIR, "terminal-host.sock"); const TOKEN_PATH = join(SUPERSET_HOME_DIR, "terminal-host.token"); const PID_PATH = join(SUPERSET_HOME_DIR, "terminal-host.pid"); const SPAWN_LOCK_PATH = join(SUPERSET_HOME_DIR, "terminal-host.spawn.lock"); +const SCRIPT_MTIME_PATH = join(SUPERSET_HOME_DIR, "terminal-host.mtime"); // Connection timeouts const CONNECT_TIMEOUT_MS = 5000; @@ -295,6 +296,18 @@ export class TerminalHostClient extends EventEmitter { */ private async connectAndAuthenticate(): Promise { for (let attempt = 0; attempt < 2; attempt++) { + if (attempt === 0 && process.env.NODE_ENV === "development") { + if (this.isDaemonScriptStale()) { + if (DEBUG_CLIENT) { + console.log( + "[TerminalHostClient] Daemon script rebuilt, restarting...", + ); + } + this.killDaemonFromPidFile(); + await this.waitForDaemonShutdown(); + } + } + let controlConnected = await this.tryConnectControl(); if (!controlConnected) { await this.spawnDaemon(); @@ -354,6 +367,47 @@ export class TerminalHostClient extends EventEmitter { throw new Error("Failed to connect after protocol upgrade"); } + /** + * Check if the daemon script has been rebuilt since the daemon was spawned. + * Only used in development mode to detect stale daemons. + */ + private isDaemonScriptStale(): boolean { + try { + if (!existsSync(SCRIPT_MTIME_PATH)) { + return false; // No mtime file = first run or manual cleanup + } + + const savedMtime = readFileSync(SCRIPT_MTIME_PATH, "utf-8").trim(); + const scriptPath = this.getDaemonScriptPath(); + + if (!existsSync(scriptPath)) { + return false; + } + + const currentMtime = statSync(scriptPath).mtimeMs.toString(); + return savedMtime !== currentMtime; + } catch { + return false; // On error, don't restart + } + } + + /** + * Save the daemon script's mtime to detect rebuilds. + */ + private saveDaemonScriptMtime(): void { + try { + const scriptPath = this.getDaemonScriptPath(); + if (!existsSync(scriptPath)) { + return; + } + + const mtime = statSync(scriptPath).mtimeMs.toString(); + writeFileSync(SCRIPT_MTIME_PATH, mtime, { mode: 0o600 }); + } catch { + // Best-effort + } + } + private killDaemonFromPidFile(): void { if (!existsSync(PID_PATH)) return; @@ -1087,6 +1141,11 @@ export class TerminalHostClient extends EventEmitter { } await this.waitForDaemon(); + // In development mode, save the script mtime to detect rebuilds + if (process.env.NODE_ENV === "development") { + this.saveDaemonScriptMtime(); + } + if (DEBUG_CLIENT) { console.log("[TerminalHostClient] Daemon started successfully"); }