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

  1. Run `bun --port=<port> --no-hmr pages/*.html` manually in the repo root and read the boot error.
  2. Confirm `bun` resolves under `/bin/zsh -lc 'which bun'` — install it or fix the login shell PATH.
  3. Free the target port (lsof -i :<port>) if a stale server holds it.
  4. 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

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

Related errors


AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12). Data as JSON: /api/errors/32c79516e80001bd. Report an issue: GitHub.