From f5705cdb9cd7443ad67b569d3e5180fa78b1dfea Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Sat, 14 Mar 2026 17:40:44 -0700 Subject: [PATCH] fix: non-blocking browser launch in Pi extension (Linux) (#292) * fix: use non-blocking spawn for browser launch in Pi extension execSync(xdg-open ...) blocks the Node.js event loop, preventing the HTTP server from responding to the browser's initial request on Linux desktops where xdg-open doesn't immediately exit. Closes #288 Co-Authored-By: Claude Opus 4.6 (1M context) * fix: handle async spawn errors in Pi openBrowser spawn() reports missing-command failures asynchronously via an 'error' event. Without a listener, a bad PLANNOTATOR_BROWSER or missing xdg-open crashes the Pi process. Add child.once("error", () => {}) to swallow it silently, matching the original execSync try/catch behavior. --------- Co-authored-by: Claude Opus 4.6 (1M context) --- apps/pi-extension/server.ts | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/pi-extension/server.ts b/apps/pi-extension/server.ts index 75795a2b..c776d217 100644 --- a/apps/pi-extension/server.ts +++ b/apps/pi-extension/server.ts @@ -7,7 +7,7 @@ */ import { createServer, type IncomingMessage, type Server } from "node:http"; -import { execSync } from "node:child_process"; +import { execSync, spawn } from "node:child_process"; import os from "node:os"; import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from "node:fs"; import { join, basename } from "node:path"; @@ -54,21 +54,34 @@ export function openBrowser(url: string): void { const platform = process.platform; const wsl = platform === "linux" && os.release().toLowerCase().includes("microsoft"); + let cmd: string; + let args: string[]; + if (browser) { if (process.env.PLANNOTATOR_BROWSER && platform === "darwin") { - execSync(`open -a ${JSON.stringify(browser)} ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = "open"; + args = ["-a", browser, url]; } else if (platform === "win32" || wsl) { - execSync(`cmd.exe /c start "" ${JSON.stringify(browser)} ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = "cmd.exe"; + args = ["/c", "start", "", browser, url]; } else { - execSync(`${JSON.stringify(browser)} ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = browser; + args = [url]; } } else if (platform === "win32" || wsl) { - execSync(`cmd.exe /c start "" ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = "cmd.exe"; + args = ["/c", "start", "", url]; } else if (platform === "darwin") { - execSync(`open ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = "open"; + args = [url]; } else { - execSync(`xdg-open ${JSON.stringify(url)}`, { stdio: "ignore" }); + cmd = "xdg-open"; + args = [url]; } + + const child = spawn(cmd, args, { detached: true, stdio: "ignore" }); + child.once("error", () => {}); + child.unref(); } catch { // Silently fail }