chenglou/pretext · error · Error
Timed out waiting for local Bun server on ${baseUrl}
Error message
Timed out waiting for local Bun server on ${baseUrl} What it means
Thrown by waitForServer() after 200 failed reachability checks (100ms apart = 20s total) against the local Bun dev server. canReachServer does an HTTP fetch and returns true only on response.ok; any connection refusal or non-2xx counts as not-ready. This gate exists specifically on the Firefox accuracy path (accuracy-check.ts:193-200) where Bun is spawned directly via zsh -lc and the script must wait for it to listen before proxying.
Source
Thrown at scripts/accuracy-check.ts:101
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function canReachServer(baseUrl: string): Promise<boolean> {
try {
const response = await fetch(baseUrl)
return response.ok
} catch {
return false
}
}
async function waitForServer(baseUrl: string): Promise<void> {
for (let i = 0; i < 200; i++) {
if (await canReachServer(baseUrl)) return
await sleep(100)
}
throw new Error(`Timed out waiting for local Bun server on ${baseUrl}`)
}
async function startProxyServer(targetOrigin: string): Promise<{ baseUrl: string, server: HttpServer }> {
const port = await getAvailablePort()
const server = createHttpServer(async (req, res) => {
try {
const targetUrl = new URL(req.url ?? '/', targetOrigin)
const response = await fetch(targetUrl, { method: req.method ?? 'GET' })
res.statusCode = response.status
response.headers.forEach((value, key) => {
if (key.toLowerCase() === 'transfer-encoding') return
res.setHeader(key, value)
})
const body = response.body === null ? new Uint8Array(0) : new Uint8Array(await response.arrayBuffer())
res.end(body)
} catch (error) {
res.statusCode = 500
res.setHeader('content-type', 'text/plain; charset=utf-8')View on GitHub (pinned to ac49b09b7d)
Solutions
- Run `bun --port=<port> --no-hmr pages/*.html` manually in the repo root and read the boot error.
- Confirm `bun` resolves under `/bin/zsh -lc 'which bun'` — install it or fix the login shell PATH.
- Free the target port (lsof -i :<port>) if a stale server holds it.
- If boot is just slow, the 200x100ms budget is hardcoded — reduce other load or warm caches before re-running.
Example fix
// before
serverProcess = spawn('/bin/zsh', ['-lc', `bun --port=${bunPort} --no-hmr pages/*.html`], {
cwd: process.cwd(),
stdio: 'ignore',
})
await waitForServer(bunBaseUrl)
// after (capture boot errors instead of ignoring stdio)
serverProcess = spawn('/bin/zsh', ['-lc', `bun --port=${bunPort} --no-hmr pages/*.html`], {
cwd: process.cwd(),
stdio: ['ignore', 'pipe', 'pipe'],
})
serverProcess.stdout?.on('data', d => process.stderr.write(`[bun] ${d}`))
serverProcess.stderr?.on('data', d => process.stderr.write(`[bun!] ${d}`))
await waitForServer(bunBaseUrl) Defensive patterns
Strategy: retry
Validate before calling
// Sanity-check the bun binary and target dir before spawning.
import { which } from './util'
if (await which('bun') === null) {
throw new Error('bun not found on PATH under /bin/zsh -lc')
} Try / catch
// Capture bun's stderr so boot failures are diagnosable instead of a 20s timeout.
serverProcess = spawn('/bin/zsh', ['-lc', `bun --port=${bunPort} --no-hmr pages/*.html`], {
cwd: process.cwd(),
stdio: ['ignore', 'pipe', 'pipe'],
})
const bootErrors: string[] = []
serverProcess.stderr?.on('data', chunk => bootErrors.push(String(chunk)))
try {
await waitForServer(bunBaseUrl)
} catch (error) {
throw new Error(`${(error as Error).message}; bun stderr: ${bootErrors.join('') || '(none)'}`)
} Prevention
- Verify `bun` resolves under `/bin/zsh -lc 'which bun'` in the same environment the script uses.
- Free the target port before running (lsof -i :<port>).
- Run the bun server command manually first to catch boot errors quickly.
- Avoid spawning during heavy machine load where the 20s budget is too tight.
When it happens
Trigger: The spawned process `bun --port=${bunPort} --no-hmr pages/*.html` fails to bind the port or crashes within 20 seconds. Concretely: bun is not installed or not on PATH under /bin/zsh -lc; the chosen port is already taken; a syntax/import error in pages/*.html causes bun to exit immediately; the OS is slow to allocate the socket.
Common situations: Running accuracy-check on a machine where bun is installed via nvm/asdf and the login shell PATH differs from the parent process; a previous Bun server did not release the port; a recent edit to an HTML/page module broke the dev server boot; heavy load making the 20s budget too tight.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out waiting for local port ${port}
- ${navigate.message ?? navigate.error}
- Timed out waiting for local Bun server on port ${port}${path
- Timed out waiting for posted report from ${browser} (last ph
- Invalid ACCURACY_CHECK_PORT: ${requestedPortRaw}
AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12).
Data as JSON: /api/errors/32c79516e80001bd.
Report an issue: GitHub.