Summary
On Windows, browse goto <url> fails 100% of the time with "Server failed to start within 15s". The root cause is a race condition in findPort() when running under the Node.js polyfill (bun-polyfill.cjs).
Environment
- Windows 11 Pro
- gstack v0.11.18.2
- Node.js v20.11.1
- Bun 1.3.11
Root cause
findPort() in server.ts tests port availability by calling Bun.serve({ port }) then immediately testServer.stop(). In real Bun, Bun.serve() is synchronous — the port binds and releases instantly.
In the Node.js polyfill, Bun.serve() wraps http.createServer() + server.listen(), which is async. stop() wraps server.close(), also async. Neither has completed by the time findPort() returns the port.
The timeline:
findPort() calls polyfill Bun.serve({ port: X }) → queues async listen()
testServer.stop() → queues async close()
findPort() returns port X (neither listen nor close has executed yet)
await browserManager.launch() — event loop runs, test server's listen() now binds port X
- Real
Bun.serve({ port: X }) → EADDRINUSE because the test server's close() hasn't completed
Reproduction
Minimal script proving the race condition:
// Save as test-race.js, run with: node test-race.js
const path = require('path');
const polyfillPath = path.join(
process.env.USERPROFILE, '.claude', 'skills', 'gstack', 'browse', 'dist', 'bun-polyfill.cjs'
);
require(polyfillPath);
async function test() {
const port = 10000 + Math.floor(Math.random() * 50000);
// Simulate findPort():
const testServer = Bun.serve({ port, fetch: () => new Response('ok') });
testServer.stop();
// Simulate browserManager.launch() delay:
await new Promise(r => setTimeout(r, 500));
// Simulate real server bind:
const http = require('http');
const s = http.createServer((req, res) => res.end('ok'));
s.on('error', (e) => {
console.log('FAILED:', e.message); // Always hits EADDRINUSE
process.exit(1);
});
s.listen(port, '127.0.0.1', () => {
console.log('SUCCESS');
s.close();
});
}
test();
This fails 100% of the time on Windows.
Fix
Replace Bun.serve() port testing in findPort() with net.createServer() using proper async bind/close:
async function findPort() {
const net = require('net');
function checkPort(port) {
return new Promise((resolve, reject) => {
const srv = net.createServer();
srv.once('error', (err) => reject(err));
srv.listen(port, '127.0.0.1', () => {
srv.close(() => resolve(port));
});
});
}
// ... same retry logic, using await checkPort(port) instead of Bun.serve()
}
This properly waits for both bind AND close to complete before returning the port.
Verified locally — 5/5 cold starts succeed, browse commands work normally.
Related issues
This is distinct from the stdio array fix in v0.11.18.2 — that fixed the spawn format, this fixes the port selection race condition.
Summary
On Windows,
browse goto <url>fails 100% of the time with "Server failed to start within 15s". The root cause is a race condition infindPort()when running under the Node.js polyfill (bun-polyfill.cjs).Environment
Root cause
findPort()inserver.tstests port availability by callingBun.serve({ port })then immediatelytestServer.stop(). In real Bun,Bun.serve()is synchronous — the port binds and releases instantly.In the Node.js polyfill,
Bun.serve()wrapshttp.createServer()+server.listen(), which is async.stop()wrapsserver.close(), also async. Neither has completed by the timefindPort()returns the port.The timeline:
findPort()calls polyfillBun.serve({ port: X })→ queues asynclisten()testServer.stop()→ queues asyncclose()findPort()returns port X (neither listen nor close has executed yet)await browserManager.launch()— event loop runs, test server'slisten()now binds port XBun.serve({ port: X })→ EADDRINUSE because the test server'sclose()hasn't completedReproduction
Minimal script proving the race condition:
This fails 100% of the time on Windows.
Fix
Replace
Bun.serve()port testing infindPort()withnet.createServer()using proper async bind/close:This properly waits for both bind AND close to complete before returning the port.
Verified locally — 5/5 cold starts succeed, browse commands work normally.
Related issues
This is distinct from the stdio array fix in v0.11.18.2 — that fixed the spawn format, this fixes the port selection race condition.