garrytan / garrytan/gstack

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

Open Beginner friendly
#2,351 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

Description

## 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`**:

```js
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):

```diff
const proc = spawn(command, args, {
stdio,
env: options.env,
cwd: options.cwd,
+ windowsHide: true,
});
```

```diff
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.

Contributor guide

Open the contributing guide

Research direction

Start in browse/src/bun-polyfill.cjs and inspect the Bun.spawn and Bun.spawnSync implementations, then review browse/src/terminal-agent-control.ts for their use. Rebuild the browse dist bundle after the change and verify on Windows that /browse no longer flashes console windows during terminal-agent respawns or version probes.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, node.js, typescript
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.