Skip to content

fix(browse): resolve Windows EADDRINUSE race condition + /health data leak#638

Open
raf-andrew wants to merge 1 commit intogarrytan:mainfrom
raf-andrew:fix/windows-browse-race-condition
Open

fix(browse): resolve Windows EADDRINUSE race condition + /health data leak#638
raf-andrew wants to merge 1 commit intogarrytan:mainfrom
raf-andrew:fix/windows-browse-race-condition

Conversation

@raf-andrew
Copy link
Copy Markdown

Summary

Root cause (#486)

findPort() in browse/src/server.ts called Bun.serve() then testServer.stop() to test if a port was free. On Windows, where Bun can't launch Chromium and the Node.js polyfill is used, Bun.serve() wraps http.createServer().listen() which is asynchronous. stop() calls server.close() without awaiting its callback — so findPort() returned a port that the test server was still holding, causing EADDRINUSE every time the real server tried to bind.

Fix: Replace Bun.serve() port testing with net.createServer() in a proper Promise wrapper that resolves only after the server.close() callback fires.

function isPortAvailable(port: number): Promise<boolean> {
  const net = require('net');
  return new Promise((resolve) => {
    const srv = net.createServer();
    srv.listen(port, '127.0.0.1', () => { srv.close(() => resolve(true)); });
    srv.on('error', () => resolve(false));
  });
}

net is available in both Bun native and Node.js, so this works on all platforms.

Security fix (#473)

Removed currentUrl from the /health response. The health endpoint requires no auth token (by design — it's needed by the CLI before the token is read). Any local process that can reach the server could poll this endpoint to observe what page the user is browsing. The field is not used by any gstack skill.

Test plan

  • Cold-start $B goto https://example.com 5× on Windows — no EADDRINUSE errors
  • $B status returns healthy without currentUrl in the JSON
  • net.createServer resolves correctly under both Bun native and node server-node.mjs

Supersedes: #490, #493

🤖 Generated with Claude Code

On Windows via the Node.js polyfill, Bun.serve() wraps http.createServer()
which is async — server.stop() doesn't wait for the port to actually be
released before returning. This causes EADDRINUSE on every cold start when
findPort() races with itself.

Fix: use net.createServer() in a Promise wrapper that resolves only after
server.close() fires its callback, guaranteeing the port is fully free.

Also removes currentUrl from the unauthenticated /health endpoint to avoid
leaking active page context to any local process that can reach the server.

Fixes: garrytan#486 (Windows EADDRINUSE race), garrytan#473 (health endpoint data leak)
Supersedes: garrytan#490, garrytan#493

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: browse server fails to start — findPort() race condition in Node.js polyfill

1 participant