fix(windows): pass windowsHide in bun-polyfill spawn/spawnSync

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 <noreply@anthropic.com>
This commit is contained in:
Oliver Southgate 2026-07-02 21:23:24 +01:00
parent 11de390be1
commit a365ace877
2 changed files with 25 additions and 0 deletions

View File

@ -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 {

View File

@ -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}');