can1357/oh-my-pi · error · ToolError
tab.goto(${JSON.stringify(url)}) timed out after ${budgetBou
Error message
tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:"domcontentloaded" What it means
`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.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:1607
signal,
url: () => page.url(),
title: () => op("tab.title()", INF, sig => untilAborted(sig, () => page.title())),
goto: (url, opts) =>
op(`tab.goto(${JSON.stringify(url)})`, INF, async sig => {
this.#clearElementCache();
try {
// Default to "load" because dev servers with HMR/WS never reach networkidle.
// budgetBound (not the full cell) so a hung navigation fails named and
// catchable inside the run instead of dying with the whole cell.
await untilAborted(sig, () =>
page.goto(url, { waitUntil: opts?.waitUntil ?? "load", timeout: budgetBound }),
);
} catch (err) {
if (err instanceof Error && err.name === "TimeoutError") {
// Abandon the hung navigation NOW — a still-pending load stalls every
// later op on this page and cascades into more opaque timeouts.
await this.#stopLoading();
throw new ToolError(
`tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:"domcontentloaded"`,
);
}
throw err;
}
}),
observe: opts => op("tab.observe()", quickOpMs, sig => this.#collectObservation({ ...opts, signal: sig })),
ariaSnapshot: (selector, opts) =>
op(
selector ? `tab.ariaSnapshot(${JSON.stringify(selector)})` : "tab.ariaSnapshot()",
quickOpMs,
async sig => {
let root: ElementHandle | null = null;
if (selector) {
root = (await untilAborted(sig, () =>
page.$(normalizeSelector(selector)),
)) as ElementHandle | null;
if (!root)View on GitHub (pinned to 9690622007)
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
Example fix
// before
await tab.goto('https://slow.example.com'); // default load, 30s budget
// after
await tab.goto('https://slow.example.com', { waitUntil: 'domcontentloaded', timeout: 60000 }); Defensive patterns
Strategy: retry
Validate before calling
// preflight reachability
const res = await fetch(url, { method: 'HEAD' }).catch(() => null);
if (!res || !res.ok) throw new Error(`unreachable before goto: ${url}`); Try / catch
try {
await tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
} catch (err) {
if (err instanceof ToolError && err.message.includes('timed out after')) {
await Bun.sleep(1000);
return tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); // one retry
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- timed out: {command}
- AnthropicConnectionTimeoutError
- xAI device-code request failed: ${error instanceof Error ? e
- xAI device-code token polling failed: ${error instanceof Err
- options.firstItemErrorMessage ?? options.errorMessage
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7e56a65e10259bb8.
Report an issue: GitHub.