{"record":{"id":"52d564aa26a71ef3","repo":"chenglou/pretext","slug":"timed-out-waiting-for-posted-report-from-browser","errorCode":null,"errorMessage":"Timed out waiting for posted report from ${browser} (last phase: ${lastPhase})","messagePattern":"Timed out waiting for posted report from (.+?) \\(last phase: (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/browser-automation.ts","lineNumber":714,"sourceCode":"    if (reportError !== null) {\n      throw reportError\n    }\n\n    const phase = await readLastNavigationPhase(session, expectedRequestId)\n    if (phase !== null) {\n      lastPhase = phase\n    }\n  }\n\n  if (resolvedReport !== null) {\n    return resolvedReport\n  }\n  if (reportError !== null) {\n    throw reportError\n  }\n\n  const observedUrl = formatObservedLocation(await session.readLocationUrl())\n  throw new Error(getTimeoutMessage(browser, 'posted report', lastPhase, observedUrl))\n}\n","sourceCodeStart":696,"sourceCodeEnd":716,"githubUrl":"https://github.com/chenglou/pretext/blob/ac49b09b7d83ede19581fa94a8b892b07d309baf/scripts/browser-automation.ts#L696-L716","documentation":"Thrown by loadPostedReport — the POST-sidechannel sibling of [23]. The host starts a local HTTP server (startPostedReportServer), hands its endpoint to the page, navigates, then races waitForReport() against a timeoutMs poll loop. If neither resolves nor rejects before the loop exhausts (and no earlier reportError threw), the harness gives up and reports the last navigation phase. Unlike [23], the report arrives via fetch POST, so size is not the issue — reachability and page execution are.","triggerScenarios":"loadPostedReport kicks off waitForReport() (draining the POST server) and concurrently polls readLastNavigationPhase every 100ms. Throw when the loop ends with resolvedReport === null && reportError === null. Concrete causes: the page never ran its POST because it errored before the fetch (lastPhase \"measuring\"); the POST hit a CORS or mixed-content block (server is http://localhost, page is file:// or a different origin); the reportEndpoint query param was malformed so the page POSTed the wrong URL; a firewall/proxy intercepted localhost; the browser tab was closed mid-flight so the fetch never fired.","commonSituations":"Firefox/Safari blocking fetch to a different origin without CORS headers on the report server; a corporate proxy or OS firewall redirecting localhost traffic; the page's report-post code path has a bug for a specific corpus; the report server was started on a port the browser cannot reach (binding to 127.0.0.1 vs the address the page resolves); race where the page POSTs before startPostedReportServer is listening (the waitForReport promise rejects but the rejection is caught into reportError — distinct from this throw).","solutions":["Distinguish from [23]: this is the POST path, so report size is not the cause — focus on reachability (CORS, firewall, endpoint URL).","Read lastPhase: \"posting\" with no report means the page tried to POST but the server never received it — check the report server is bound to an address reachable from the browser and emits permissive CORS headers.","Confirm the reportEndpoint query parameter in the URL exactly matches startPostedReportServer's endpoint (encoding mismatch is the most common silent failure).","Watch the page's network tab in a headed run: a failed POST (blocked/CORS/404) shows up there even though loadPostedReport only sees silence.","Raise --timeout if the page is genuinely slow (large sweep) rather than blocked."],"exampleFix":"// before\nconst report = await loadPostedReport(session, url, () => reportServer.waitForReport(null), requestId, browser, timeoutMs)\n// after — bind the report server to all interfaces only if the browser cannot reach 127.0.0.1, and log endpoint mismatch\nconst reportServer = await startPostedReportServer<CorpusReport>(requestId)\nconst urlWithEndpoint = `${url}&reportEndpoint=${encodeURIComponent(reportServer.endpoint)}`\nif (!urlWithEndpoint.includes(`reportEndpoint=${encodeURIComponent(reportServer.endpoint)}`)) {\n  throw new Error(`reportEndpoint encoding mismatch; page will POST to the wrong URL`)\n}\nconst report = await loadPostedReport(session, urlWithEndpoint, () => reportServer.waitForReport(null), requestId, browser, timeoutMs)","handlingStrategy":"try-catch","validationCode":"// Verify the reportEndpoint is reachable from the browser BEFORE navigating\nasync function canReachReportEndpoint(endpoint: string): Promise<boolean> {\n  try {\n    const res = await fetch(endpoint, { method: 'OPTIONS' })\n    return res.ok || res.status === 404 // 404 is fine — server exists, just no OPTIONS route\n  } catch {\n    return false\n  }\n}\nif (!(await canReachReportEndpoint(reportServer.endpoint))) {\n  throw new Error(`reportEndpoint ${reportServer.endpoint} not reachable; the page POST will never arrive.`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await loadPostedReport(session, urlWithEndpoint, () => reportServer.waitForReport(null), requestId, browser, timeoutMs)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('last phase: posting')) {\n    // POST never landed — re-verify endpoint/CORS rather than retrying blindly\n    throw new Error(`${error.message}. Verify reportEndpoint reachability and CORS for ${reportServer.endpoint}.`)\n  }\n  throw error\n}","preventionTips":["URL-encode reportServer.endpoint into the page URL — a bare `&` or `:` will silently redirect the POST.","Ensure startPostedReportServer binds to an address the browser can reach (127.0.0.1 usually fine locally; not if the browser is remote/headless on another host).","Confirm the report server emits permissive CORS headers; cross-origin POSTs from the page otherwise fail preflight.","Keep requestId unique per attempt so a late POST from a previous run cannot satisfy the current waitForReport."],"tags":["browser-automation","timeout","report","post-transport","network"],"backgroundTag":null,"analyzedSha":"ac49b09b7d83ede19581fa94a8b892b07d309baf","analyzedAt":"2026-08-12T17:03:16.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}