chenglou/pretext · error · Error
Timed out waiting for ${browser} automation lock
Error message
Timed out waiting for ${browser} automation lock What it means
Thrown by acquireBrowserAutomationLock() when it cannot obtain the exclusive lock file (LOCK_DIR/<browser>.lock, under TMPDIR/pretext-browser-automation-locks) within timeoutMs (default 120s). The lock is per-browser and created with O_EXCL (open 'wx'); on EEXIST it checks if the owning pid is still alive and, if dead, self-heals by removing the stale lock and retrying. Only if a live owner holds it for the full timeout does this throw.
Source
Thrown at scripts/browser-automation.ts:211
rmSync(lockPath)
} catch {
// Best effort cleanup.
}
},
}
} catch (error) {
if (!(error instanceof Error) || !String(error).includes('EEXIST')) throw error
const metadata = readLockMetadata(lockPath)
if (metadata !== null && !isProcessAlive(metadata.pid)) {
try {
rmSync(lockPath)
continue
} catch {
// Another process may have replaced or removed it. Retry normally.
}
}
if (Date.now() - start >= timeoutMs) {
throw new Error(`Timed out waiting for ${browser} automation lock`)
}
await sleep(250)
}
}
}
async function canReachUrl(url: string): Promise<boolean> {
try {
const response = await fetch(url)
return response.ok
} catch {
return false
}
}
const LOOPBACK_BASES = [
'http://127.0.0.1',
'http://localhost',View on GitHub (pinned to ac49b09b7d)
Solutions
- Check for a live owner: read the pid from the lock file (TMPDIR/pretext-browser-automation-locks/<browser>.lock) and inspect that process.
- If the owner is genuinely stuck or orphaned, kill it and remove the lock file manually.
- Serialize browser-automation jobs per browser (do not run corpus-sweep and accuracy-check in parallel for the same browser).
- If you need a shorter/longer wait, pass a custom timeoutMs to acquireBrowserAutomationLock.
Example fix
# before: two concurrent jobs race the same browser lock bun run scripts/accuracy-check.ts & bun run scripts/corpus-sweep.ts & # after: serialize per browser bun run scripts/accuracy-check.ts && bun run scripts/corpus-sweep.ts # or manually clear a dead-owner lock rm "$TMPDIR/pretext-browser-automation-locks/chrome.lock"
Defensive patterns
Strategy: validation
Validate before calling
// Check the lock before acquiring; report the live owner if held.
import { readLockMetadata, isProcessAlive } from './browser-automation'
const meta = readLockMetadata(lockPath)
if (meta !== null && isProcessAlive(meta.pid)) {
console.error(`Lock held by live pid ${meta.pid}; waiting up to 120s`)
} Try / catch
// Wrap acquireBrowserAutomationLock so lock contention is a clean message, not a crash.
let lock
try {
lock = await acquireBrowserAutomationLock(browser)
} catch (error) {
console.error(`Could not acquire ${browser} lock: ${(error as Error).message}`)
console.error(`Remove $TMPDIR/pretext-browser-automation-locks/${browser}.lock if stale.`)
process.exit(1)
} Prevention
- Serialize browser-automation jobs per browser; never run two against the same browser.
- Ensure scripts release the lock in a finally block (the harness does).
- If a job crashes, manually remove TMPDIR/pretext-browser-automation-locks/<browser>.lock.
- Use tman/process supervision so hung owners are reaped.
When it happens
Trigger: Another accuracy-check/benchmark-check/corpus-sweep process is already running for the same browser and holds the lock; a previous process is hung (alive pid but stuck); or two scripts started near-simultaneously and one starved the other past 120s. The single-owner-per-browser rule (documented in AGENTS.md) is the design reason.
Common situations: Running corpus-sweep and accuracy-check concurrently against Chrome; a prior checker that crashed without releasing the lock but whose shell wrapper process is still alive; a hung browser automation session; CI scheduling overlapping jobs for the same browser.
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}
- Timed out waiting for local Bun server on port ${port}${path
- Timed out waiting for report from ${browser} (last phase: ${
- Timed out waiting for posted report from ${browser} (last ph
- Timed out waiting for local Bun server on ${baseUrl}
AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12).
Data as JSON: /api/errors/be246b935659b762.
Report an issue: GitHub.