{"record":{"id":"7e56a65e10259bb8","repo":"can1357/oh-my-pi","slug":"tab-goto-json-stringify-url-timed-out-after","errorCode":null,"errorMessage":"tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:\"domcontentloaded\"","messagePattern":"tab\\.goto\\((.+?)\\) timed out after (.+?)ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:\"domcontentloaded\"","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-worker.ts","lineNumber":1607,"sourceCode":"\t\t\tsignal,\n\t\t\turl: () => page.url(),\n\t\t\ttitle: () => op(\"tab.title()\", INF, sig => untilAborted(sig, () => page.title())),\n\t\t\tgoto: (url, opts) =>\n\t\t\t\top(`tab.goto(${JSON.stringify(url)})`, INF, async sig => {\n\t\t\t\t\tthis.#clearElementCache();\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Default to \"load\" because dev servers with HMR/WS never reach networkidle.\n\t\t\t\t\t\t// budgetBound (not the full cell) so a hung navigation fails named and\n\t\t\t\t\t\t// catchable inside the run instead of dying with the whole cell.\n\t\t\t\t\t\tawait untilAborted(sig, () =>\n\t\t\t\t\t\t\tpage.goto(url, { waitUntil: opts?.waitUntil ?? \"load\", timeout: budgetBound }),\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") {\n\t\t\t\t\t\t\t// Abandon the hung navigation NOW — a still-pending load stalls every\n\t\t\t\t\t\t\t// later op on this page and cascades into more opaque timeouts.\n\t\t\t\t\t\t\tawait this.#stopLoading();\n\t\t\t\t\t\t\tthrow new ToolError(\n\t\t\t\t\t\t\t\t`tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:\"domcontentloaded\"`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow err;\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\tobserve: opts => op(\"tab.observe()\", quickOpMs, sig => this.#collectObservation({ ...opts, signal: sig })),\n\t\t\tariaSnapshot: (selector, opts) =>\n\t\t\t\top(\n\t\t\t\t\tselector ? `tab.ariaSnapshot(${JSON.stringify(selector)})` : \"tab.ariaSnapshot()\",\n\t\t\t\t\tquickOpMs,\n\t\t\t\t\tasync sig => {\n\t\t\t\t\t\tlet root: ElementHandle | null = null;\n\t\t\t\t\t\tif (selector) {\n\t\t\t\t\t\t\troot = (await untilAborted(sig, () =>\n\t\t\t\t\t\t\t\tpage.$(normalizeSelector(selector)),\n\t\t\t\t\t\t\t)) as ElementHandle | null;\n\t\t\t\t\t\t\tif (!root)","sourceCodeStart":1589,"sourceCodeEnd":1625,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-worker.ts#L1589-L1625","documentation":"`tab.goto` exceeded its navigation time budget (TimeoutError from Puppeteer); the worker proactively calls #stopLoading() to abandon the hung navigation before throwing, because a still-pending load stalls all subsequent page operations. The message suggests retrying with a longer timeout or a less strict waitUntil.","triggerScenarios":"Navigating to a slow/unreachable URL where the load event does not fire within budgetMs: slow network, hanging subresources, server never responding, or a page that never fires `load` (long-polling scripts, infinite spinners).","commonSituations":"Heavy pages with third-party analytics/ad scripts blocking load; captive-portal or VPN slowness; sites with perpetual network activity (dashboards, streams); cold-start of a lazily provisioned server.","solutions":["Retry with waitUntil:\"domcontentloaded\" instead of the default load — DOM is usually ready long before all resources finish","Increase the tool/navigation timeout budget for this URL","Call #stopLoading/`page.waitForNetworkIdle` alternatives or block slow third-party origins via request interception","Check URL reachability (curl/DNS) — if the server is genuinely down, fix that first"],"exampleFix":"// before\nawait tab.goto('https://slow.example.com'); // default load, 30s budget\n// after\nawait tab.goto('https://slow.example.com', { waitUntil: 'domcontentloaded', timeout: 60000 });","handlingStrategy":"retry","validationCode":"// preflight reachability\nconst res = await fetch(url, { method: 'HEAD' }).catch(() => null);\nif (!res || !res.ok) throw new Error(`unreachable before goto: ${url}`);","typeGuard":null,"tryCatchPattern":"try {\n  await tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes('timed out after')) {\n    await Bun.sleep(1000);\n    return tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); // one retry\n  }\n  throw err;\n}","preventionTips":["Prefer waitUntil:\"domcontentloaded\" for heavy pages","Set generous per-URL timeout budgets for known-slow sites","Block slow third-party origins via request interception","Check URL reachability/DNS before blaming the browser"],"tags":["navigation","timeout","puppeteer","network"],"backgroundTag":"navigation-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}