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

  1. Retry with waitUntil:"domcontentloaded" instead of the default load — DOM is usually ready long before all resources finish
  2. Increase the tool/navigation timeout budget for this URL
  3. Call #stopLoading/`page.waitForNetworkIdle` alternatives or block slow third-party origins via request interception
  4. 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

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

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/7e56a65e10259bb8. Report an issue: GitHub.