Skip to content

Windows: /browse daemon spawns bun without windowsHide → console window flashes and steals focus #2351

Description

@abolrous

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

  1. On Windows, install bun via WinGet.
  2. Run any /browse-backed skill (or just leave a browse daemon running).
  3. Observe console windows citing ...\WinGet\Links\bun.exe popping up and stealing focus, especially on the 60s watchdog respawn.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions