Summary
On Windows, the /browse daemon repeatedly pops a console window that steals foreground focus (interrupting typing and voice dictation). The window shows the path of the WinGet bun.exe shim, e.g. ...\WinGet\Links\bun.exe.
Environment
- Windows 11
- gstack
1.51.0.0
bun installed via WinGet (a console-subsystem exe on PATH as ...\Microsoft\WinGet\Links\bun.exe)
- Browse daemon running under the Node fallback (
browse/dist/server-node.mjs + browse/dist/bun-polyfill.cjs), which is the Windows path since Bun can't drive Playwright's Chromium on Windows.
Root cause
On Windows the browse server runs under Node with browse/src/bun-polyfill.cjs shimming the Bun API. Its Bun.spawn / Bun.spawnSync implementations call Node's child_process.spawn / spawnSync without windowsHide: true:
spawn(cmd, options = {}) {
const [command, ...args] = cmd;
const proc = spawn(command, args, {
stdio,
env: options.env,
cwd: options.cwd,
// no windowsHide <-- here
});
...
}
spawnTerminalAgent (browse/src/terminal-agent-control.ts) launches bun run terminal-agent.ts through this shim. Because bun resolves to a console executable, Windows allocates a fresh console window (conhost) for every spawn, which pops up and grabs foreground.
It recurs continuously because the agent-health watchdog in browse/src/server.ts (~line 1481, 60s tick) respawns the terminal-agent whenever its recorded PID dies. A daemon left over from an earlier /browse keeps flashing even with no browse session open.
Impact
Constant focus theft breaks typing and speech-to-text on Windows. HKCU\Control Panel\Desktop\ForegroundLockTimeout only softens the focus grab; the window still pops and flickers over the terminal.
Fix
Pass windowsHide: true in the options for both spawn and spawnSync in browse/src/bun-polyfill.cjs (then rebuild the dist bundle):
const proc = spawn(command, args, {
stdio,
env: options.env,
cwd: options.cwd,
+ windowsHide: true,
});
const result = spawnSync(command, args, {
stdio: [ ... ],
timeout: options.timeout,
env: options.env,
cwd: options.cwd,
+ windowsHide: true,
});
windowsHide: true sets CREATE_NO_WINDOW, so no console is ever allocated. This also silences the version-probe spawnSync calls (e.g. chrome --version). Verified locally: after the patch, restarting the browse daemon eliminates the flashing entirely with no functional change.
Repro
- On Windows, install
bun via WinGet.
- Run any
/browse-backed skill (or just leave a browse daemon running).
- Observe console windows citing
...\WinGet\Links\bun.exe popping up and stealing focus, especially on the 60s watchdog respawn.
Summary
On Windows, the
/browsedaemon repeatedly pops a console window that steals foreground focus (interrupting typing and voice dictation). The window shows the path of the WinGetbun.exeshim, e.g....\WinGet\Links\bun.exe.Environment
1.51.0.0buninstalled via WinGet (a console-subsystem exe on PATH as...\Microsoft\WinGet\Links\bun.exe)browse/dist/server-node.mjs+browse/dist/bun-polyfill.cjs), which is the Windows path since Bun can't drive Playwright's Chromium on Windows.Root cause
On Windows the browse server runs under Node with
browse/src/bun-polyfill.cjsshimming the Bun API. ItsBun.spawn/Bun.spawnSyncimplementations call Node'schild_process.spawn/spawnSyncwithoutwindowsHide: true:spawnTerminalAgent(browse/src/terminal-agent-control.ts) launchesbun run terminal-agent.tsthrough this shim. Becausebunresolves to a console executable, Windows allocates a fresh console window (conhost) for every spawn, which pops up and grabs foreground.It recurs continuously because the agent-health watchdog in
browse/src/server.ts(~line 1481, 60s tick) respawns the terminal-agent whenever its recorded PID dies. A daemon left over from an earlier/browsekeeps flashing even with no browse session open.Impact
Constant focus theft breaks typing and speech-to-text on Windows.
HKCU\Control Panel\Desktop\ForegroundLockTimeoutonly softens the focus grab; the window still pops and flickers over the terminal.Fix
Pass
windowsHide: truein the options for bothspawnandspawnSyncinbrowse/src/bun-polyfill.cjs(then rebuild thedistbundle):const proc = spawn(command, args, { stdio, env: options.env, cwd: options.cwd, + windowsHide: true, });const result = spawnSync(command, args, { stdio: [ ... ], timeout: options.timeout, env: options.env, cwd: options.cwd, + windowsHide: true, });windowsHide: truesetsCREATE_NO_WINDOW, so no console is ever allocated. This also silences the version-probespawnSynccalls (e.g.chrome --version). Verified locally: after the patch, restarting the browse daemon eliminates the flashing entirely with no functional change.Repro
bunvia WinGet./browse-backed skill (or just leave a browse daemon running)....\WinGet\Links\bun.exepopping up and stealing focus, especially on the 60s watchdog respawn.