Skip to content

Windows: terminal-agent spawns a visible console window that steals keyboard focus #2575

Description

@garrett-fox

Symptom

While the browse daemon is alive, a console window titled C:\Users\<user>\.bun\bin\bun.exe
appears repeatedly and takes foreground focus. On Windows a newly created console window
steals keyboard input, so it interrupts whatever the user is typing in another application.
It recurs rather than happening once, which makes browse effectively unusable on an
attended desktop.

Environment

  • Windows 11 Pro (26200), Git Bash / MSYS2
  • browse build 11de390be1be6849eb9a15f91ff4922dd16c589a
  • Headless (launched) mode — not --headed

Root cause

src/terminal-agent-control.ts, in spawnTerminalAgent():

const proc = (Bun as any).spawn(['bun', 'run', script], {
  cwd: opts.cwd || process.cwd(),
  env: { ...process.env, BROWSE_STATE_FILE: opts.stateFile, /* ... */ },
  stdio: ['ignore', 'ignore', 'ignore'],
});
proc.unref?.();

The call already passes stdio: ['ignore','ignore','ignore'] and calls unref(), but there
is no windowsHide option — and on Windows, redirecting stdio does not stop the OS from
allocating a console window for the child. The watchdog then respawns the agent whenever it
exits, which reproduces the window each time and is why closing it doesn't help.

Confirmed by process inspection — the window belongs to a direct child of the daemon, not to
the daemon itself:

ProcessId       : 55380
ParentProcessId : 51412
CommandLine     : bun run <...>\browse\src\terminal-agent.ts

ProcessId       : 51412
Name            : node.exe
CommandLine     : node.exe <...>\browse\dist\server-node.mjs

This matters for anyone debugging it: killing the visible bun.exe by name leaves the real
node.exe daemon alive to spawn another within seconds, which looks exactly like "it keeps
popping back up no matter how many times I close it."

Why this looks like an oversight rather than a design choice

src/cli.ts already handles precisely this class of problem for the server launch, with a
comment saying so (~lines 318–328): it notes that Bun.spawn() + proc.unref() doesn't
truly detach on Windows, and routes the launch through Node's child_process.spawn with
{detached: true, stdio: ['ignore','ignore','ignore']} instead. The terminal-agent spawn
never got the same treatment.

Suggested fix

Add windowsHide: true to the spawn options. If Bun.spawn does not honour windowsHide
on Windows, route this spawn through Node's child_process.spawn the same way cli.ts
already does for the server:

import { spawn as nodeSpawn } from 'child_process';

nodeSpawn(process.execPath, [script], {
  detached: true,
  stdio: ['ignore', 'ignore', 'ignore'],
  windowsHide: true,   // <-- suppresses the console window
  env: { /* ... */ },
}).unref();

Two other Bun.spawn call sites spawn bun as a child and look like they'd have the same
behaviour on Windows, worth auditing in the same pass:

  • src/browser-skill-commands.ts:188 — Bun.spawn(['bun', 'test', testFile], …)
  • src/browser-skill-commands.ts:266 — Bun.spawn(['bun', 'run', scriptPath, …], …)

Workaround

browse stop tears down both the daemon and the bun child, which stops the popups.

Secondary, minor

browse stop prints:

[browse] Unable to connect. Is the computer able to access the url?

and still succeeds — both processes do exit (verified by checking for the server-node.mjs
and bun.exe processes immediately afterward; nothing matched). The message reads like a
failure and invites a pointless retry or a manual hunt for surviving processes. It looks like
stop is being routed through the goto path and treated as a URL. Suggest suppressing that
line, or printing an explicit stopped confirmation instead.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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