chenglou/pretext · error · Error
Timed out waiting for local port ${port}
Error message
Timed out waiting for local port ${port} What it means
Thrown by waitForPort() in browser-automation.ts after 200 failed TCP connection attempts (100ms apart = 20s) to 127.0.0.1:<port>. Unlike waitForServer (error 4) which does HTTP fetches, this opens a raw net socket and checks for 'connect' vs 'error'. It is used to wait for WebDriver/BiDi debugging ports (not HTTP servers), so an HTTP-unreachable but TCP-open port still counts as ready. If the target process never listens within 20s, it throws.
Source
Thrown at scripts/browser-automation.ts:93
for (let i = 0; i < 200; i++) {
const open = await new Promise<boolean>(resolve => {
const socket = createConnection({ host: '127.0.0.1', port })
let settled = false
const finish = (value: boolean): void => {
if (settled) return
settled = true
socket.destroy()
resolve(value)
}
socket.once('connect', () => finish(true))
socket.once('error', () => finish(false))
})
if (open) return
await sleep(100)
}
throw new Error(`Timed out waiting for local port ${port}`)
}
export async function getAvailablePort(requestedPort: number | null = null): Promise<number> {
if (requestedPort !== null && Number.isFinite(requestedPort) && requestedPort > 0) {
return requestedPort
}
return await new Promise((resolve, reject) => {
const server = createNetServer()
server.once('error', reject)
server.listen(0, '127.0.0.1', () => {
const address = server.address()
if (address === null || typeof address === 'string') {
reject(new Error('Failed to allocate a free port'))
return
}
const { port } = addressView on GitHub (pinned to ac49b09b7d)
Solutions
- Confirm Firefox is installed at /Applications/Firefox.app/Contents/MacOS/firefox (the path is hardcoded at browser-automation.ts:360).
- Quit any running Firefox instance before running the script (--new-instance requires no existing process).
- Manually launch Firefox with the same flags to see the startup error.
- Clear stale profile dirs under mkdtemp's tmpdir prefix 'pretext-firefox-'.
Defensive patterns
Strategy: retry
Validate before calling
// Confirm the Firefox binary exists before spawning.
import { existsSync } from 'node:fs'
if (!existsSync('/Applications/Firefox.app/Contents/MacOS/firefox')) {
throw new Error('Firefox not found at /Applications/Firefox.app')
} Try / catch
// Add a retry around waitForPort for transient startup races.
async function waitForPortWithRetry(port: number, attempts = 3): Promise<void> {
for (let i = 0; i < attempts; i++) {
try { await waitForPort(port); return }
catch { if (i === attempts - 1) throw }
}
} Prevention
- Quit any running Firefox before launching the script (--new-instance needs exclusivity).
- Ensure Firefox is installed at the hardcoded /Applications path.
- Clear stale profile dirs with the 'pretext-firefox-' tmp prefix.
- Run Firefox manually with the same flags to catch startup errors early.
When it happens
Trigger: The Firefox remote-debugging port (initializeFirefoxSession) never accepts connections within 20s — Firefox failed to launch, crashed on startup, or the port was wrong. Also any other caller of waitForPort whose target process did not bind in time.
Common situations: Firefox is not installed at the hardcoded /Applications/Firefox.app path; Firefox is already running and refusing --new-instance; the spawned profile is corrupted; macOS Gatekeeper quarantining the binary; heavy system load delaying Firefox startup beyond 20s.
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 Bun server on ${baseUrl}
- ${navigate.message ?? navigate.error}
- Timed out waiting for posted report from ${browser} (last ph
- Timed out waiting for ${browser} automation lock
- ${session.message ?? session.error}
AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12).
Data as JSON: /api/errors/8a5eaf9ddffc850a.
Report an issue: GitHub.