{"record":{"id":"be246b935659b762","repo":"chenglou/pretext","slug":"timed-out-waiting-for-browser-automation-lock","errorCode":null,"errorMessage":"Timed out waiting for ${browser} automation lock","messagePattern":"Timed out waiting for (.+?) automation lock","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/browser-automation.ts","lineNumber":211,"sourceCode":"            rmSync(lockPath)\n          } catch {\n            // Best effort cleanup.\n          }\n        },\n      }\n    } catch (error) {\n      if (!(error instanceof Error) || !String(error).includes('EEXIST')) throw error\n      const metadata = readLockMetadata(lockPath)\n      if (metadata !== null && !isProcessAlive(metadata.pid)) {\n        try {\n          rmSync(lockPath)\n          continue\n        } catch {\n          // Another process may have replaced or removed it. Retry normally.\n        }\n      }\n      if (Date.now() - start >= timeoutMs) {\n        throw new Error(`Timed out waiting for ${browser} automation lock`)\n      }\n      await sleep(250)\n    }\n  }\n}\n\nasync function canReachUrl(url: string): Promise<boolean> {\n  try {\n    const response = await fetch(url)\n    return response.ok\n  } catch {\n    return false\n  }\n}\n\nconst LOOPBACK_BASES = [\n  'http://127.0.0.1',\n  'http://localhost',","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/chenglou/pretext/blob/ac49b09b7d83ede19581fa94a8b892b07d309baf/scripts/browser-automation.ts#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: two concurrent jobs race the same browser lock\nbun run scripts/accuracy-check.ts &\nbun run scripts/corpus-sweep.ts &\n\n# after: serialize per browser\nbun run scripts/accuracy-check.ts && bun run scripts/corpus-sweep.ts\n# or manually clear a dead-owner lock\nrm \"$TMPDIR/pretext-browser-automation-locks/chrome.lock\"","handlingStrategy":"validation","validationCode":"// Check the lock before acquiring; report the live owner if held.\nimport { readLockMetadata, isProcessAlive } from './browser-automation'\nconst meta = readLockMetadata(lockPath)\nif (meta !== null && isProcessAlive(meta.pid)) {\n  console.error(`Lock held by live pid ${meta.pid}; waiting up to 120s`)\n}","typeGuard":null,"tryCatchPattern":"// Wrap acquireBrowserAutomationLock so lock contention is a clean message, not a crash.\nlet lock\ntry {\n  lock = await acquireBrowserAutomationLock(browser)\n} catch (error) {\n  console.error(`Could not acquire ${browser} lock: ${(error as Error).message}`)\n  console.error(`Remove $TMPDIR/pretext-browser-automation-locks/${browser}.lock if stale.`)\n  process.exit(1)\n}","preventionTips":["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."],"tags":["browser-automation","concurrency","file-lock","timeout","single-owner"],"backgroundTag":null,"analyzedSha":"ac49b09b7d83ede19581fa94a8b892b07d309baf","analyzedAt":"2026-08-12T17:03:16.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}