From a365ace877f6fd601096124bd322a3dc9f5c46a0 Mon Sep 17 00:00:00 2001 From: Oliver Southgate Date: Thu, 2 Jul 2026 21:23:24 +0100 Subject: [PATCH] fix(windows): pass windowsHide in bun-polyfill spawn/spawnSync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows the browse server always runs under Node via server-node.mjs + bun-polyfill.cjs (the Bun path cannot drive Playwright Chromium). The polyfill forwarded only stdio/env/cwd to child_process, dropping windowsHide — and node defaults it to false, so every subprocess the console-less daemon spawned (terminal-agent respawns via the v1.44 watchdog, tasklist probes, git, icacls) flashed a visible console window. Pass windowsHide: true unconditionally and forward detached so daemon children behave as the Bun-runtime call sites intend. Static tripwire test pins both spawn paths; call-site fixes like the one proposed in #2019 are no longer silently swallowed on the node path. Fixes the flashing-bun.exe-windows symptom in #2151. Co-Authored-By: Claude Fable 5 --- browse/src/bun-polyfill.cjs | 7 +++++++ browse/test/bun-polyfill.test.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/browse/src/bun-polyfill.cjs b/browse/src/bun-polyfill.cjs index e0ada11b3..ecfd26efe 100644 --- a/browse/src/bun-polyfill.cjs +++ b/browse/src/bun-polyfill.cjs @@ -75,6 +75,9 @@ globalThis.Bun = { timeout: options.timeout, env: options.env, cwd: options.cwd, + // The browse server runs console-less on Windows; without this every + // subprocess (tasklist, git, icacls) pops a visible console window. + windowsHide: true, }); return { @@ -91,6 +94,10 @@ globalThis.Bun = { stdio, env: options.env, cwd: options.cwd, + detached: options.detached, + // Same as spawnSync: detached daemons (terminal-agent respawns) must + // not flash a console window on Windows. + windowsHide: true, }); return { diff --git a/browse/test/bun-polyfill.test.ts b/browse/test/bun-polyfill.test.ts index 7ca25dfab..f9c3dafe6 100644 --- a/browse/test/bun-polyfill.test.ts +++ b/browse/test/bun-polyfill.test.ts @@ -1,4 +1,5 @@ import { describe, test, expect, afterAll } from 'bun:test'; +import * as fs from 'fs'; import * as path from 'path'; // Load the polyfill into a fresh object (don't clobber globalThis.Bun) @@ -48,6 +49,23 @@ describe('bun-polyfill', () => { expect(lines[2]).toBe('HAS_UNREF'); }); + test('spawn and spawnSync pass windowsHide (no console flash from console-less daemons)', () => { + // Static tripwire (#2151): on Windows the browse server always runs + // under Node via this polyfill, and node's child_process defaults to + // windowsHide: false — so every subprocess the console-less daemon + // spawns (terminal-agent respawns, git, icacls) pops a visible console + // window. Both spawn paths must pass windowsHide: true, and spawn must + // forward `detached` for daemon children. Behavioral assertion isn't + // possible cross-platform (window creation is invisible to the child), + // hence source-level pinning. + const src = fs.readFileSync(polyfillPath, 'utf-8'); + const spawnSyncBlock = src.slice(src.indexOf('spawnSync(cmd'), src.indexOf('spawn(cmd')); + const spawnBlock = src.slice(src.indexOf('spawn(cmd')); + expect(spawnSyncBlock).toContain('windowsHide: true'); + expect(spawnBlock).toContain('windowsHide: true'); + expect(spawnBlock).toContain('detached: options.detached'); + }); + test('Bun.serve creates an HTTP server that responds', async () => { const result = Bun.spawnSync(['node', '-e', ` require('${polyfillPath}');