From 270f1a038a18130d45574c85288b2e066d30dfbd Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 14 Aug 2026 12:53:33 -0700 Subject: [PATCH] fix(browse): windowsHide on every Windows-reachable spawn (#1835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Console windows flashed (and stole focus) on every daemon relaunch, taskkill, tasklist poll, and powershell DPAPI call — node-level spawns default windowsHide to false. Covered: the node -e launcher (outer spawnSync AND the inner detached daemon spawn inside the launcher string), the dev-mode bun fallback, killServer's taskkill, isProcessAlive's tasklist, and cookie-import's powershell + tasklist. The Bun-polyfill shims were covered by absorbed PRs #2523 + #2539 (thanks @jwilk-hrep, @jerrynicholsai); this closes the sites those PRs didn't reach. The icacls sites land with the #1605 DACL commit alongside the static tripwire that pins all of them. R8's planned spawnHidden() helper is deliberately NOT built: the polyfill default plus the tripwire achieve the no-drift goal without indirection over seven heterogeneous call shapes. The polyfill + spawn-hide tests join the Windows CI shard. Ported from time-attack/gstack (GStack 2). Co-authored-by: Sina Matian Co-Authored-By: Claude Fable 5 --- .github/workflows/windows-free-tests.yml | 2 ++ browse/src/cli.ts | 7 ++++--- browse/src/cookie-import-browser.ts | 3 ++- browse/src/error-handling.ts | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows-free-tests.yml b/.github/workflows/windows-free-tests.yml index c05a7d002..7435814cc 100644 --- a/.github/workflows/windows-free-tests.yml +++ b/.github/workflows/windows-free-tests.yml @@ -111,6 +111,8 @@ jobs: browse/test/claude-bin.test.ts \ test/test-free-shards.test.ts \ browse/test/file-permissions.test.ts \ + browse/test/bun-polyfill.test.ts \ + browse/test/windows-spawn-hide.test.ts \ browse/test/security.test.ts \ browse/test/server-sanitize-surrogates.test.ts \ test/setup-windows-fallback.test.ts \ diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 213471f54..ed6703901 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -143,7 +143,7 @@ async function killServer(pid: number): Promise { try { Bun.spawnSync( ['taskkill', '/PID', String(pid), '/T', '/F'], - { stdout: 'pipe', stderr: 'pipe', timeout: 5000 } + { stdout: 'pipe', stderr: 'pipe', timeout: 5000, windowsHide: true } ); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; @@ -323,9 +323,9 @@ async function startServer(extraEnv?: Record): Promise): Promise { ].join('; '); const proc = Bun.spawn(['powershell', '-NoProfile', '-Command', script], { + windowsHide: true, stdin: 'pipe', stdout: 'pipe', stderr: 'pipe', @@ -778,7 +779,7 @@ function isBrowserRunning(browserName: string): Promise { const exe = browserName.toLowerCase().includes('edge') ? 'msedge.exe' : 'chrome.exe'; return new Promise((resolve) => { const proc = Bun.spawn(['tasklist', '/FI', `IMAGENAME eq ${exe}`, '/NH'], { - stdout: 'pipe', stderr: 'pipe', + stdout: 'pipe', stderr: 'pipe', windowsHide: true, }); proc.exited.then(async () => { const out = await new Response(proc.stdout).text(); diff --git a/browse/src/error-handling.ts b/browse/src/error-handling.ts index 2c4e271e8..3be921fa2 100644 --- a/browse/src/error-handling.ts +++ b/browse/src/error-handling.ts @@ -42,7 +42,7 @@ export function isProcessAlive(pid: number): boolean { try { const result = Bun.spawnSync( ['tasklist', '/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], - { stdout: 'pipe', stderr: 'pipe', timeout: 3000 } + { stdout: 'pipe', stderr: 'pipe', timeout: 3000, windowsHide: true } ); return result.stdout.toString().includes(`"${pid}"`); } catch {