can1357/oh-my-pi · error · ToolError

tab.waitForUrl() timed out after ${timeoutMs}ms

Error message

tab.waitForUrl() timed out after ${timeoutMs}ms

What it means

CmuxTab.waitForUrl(pattern, opts) polls browser.url.get every 200ms until the current URL matches the given RegExp and throws this ToolError when the deadline (opts.timeout, run-context timeout, or 30s default) expires first. Note: when pattern is a plain string, a native browser.wait is used instead and this error only comes from the RegExp polling path.

Source

Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:645

				this.#lastUrl = result.url;
			}
			return this.#lastUrl;
		}
		const deadline = Date.now() + timeoutMs;
		while (Date.now() <= deadline) {
			const result = (await this.#request(
				"browser.url.get",
				{},
				Math.min(timeoutMs, 5_000),
				signal,
			)) as CmuxUrlGetResult;
			if (typeof result.url === "string" && result.url.length > 0) {
				this.#lastUrl = result.url;
				if (pattern.test(result.url)) return result.url;
			}
			await untilAborted(signal, () => Bun.sleep(200));
		}
		throw new ToolError(`tab.waitForUrl() timed out after ${timeoutMs}ms`);
	}

	async waitForNavigation(opts?: { waitUntil?: WaitUntil; timeout?: number }): Promise<null> {
		const timeoutMs = opts?.timeout ?? this.#runContext?.timeoutMs ?? 30_000;
		const signal = this.#runContext?.signal;
		// Cmux has no native "next navigation" wait — snapshot the current URL via a fresh
		// `browser.url.get` (never the possibly-stale `#lastUrl`), then poll for a change
		// from it (mirroring headless `page.waitForNavigation` intent) and optionally settle
		// on the requested load state. Start it BEFORE the click/submit that navigates; after
		// a completed nav it times out like puppeteer does.
		const baseline = (await this.#request(
			"browser.url.get",
			{},
			Math.min(timeoutMs, 5_000),
			signal,
		)) as CmuxUrlGetResult;
		const startUrl = typeof baseline.url === "string" && baseline.url.length > 0 ? baseline.url : this.#lastUrl;
		if (typeof baseline.url === "string" && baseline.url.length > 0) this.#lastUrl = baseline.url;

View on GitHub (pinned to 9690622007)

Solutions

  1. Increase opts.timeout (e.g. tab.waitForUrl(/dashboard/, { timeout: 60000 })).
  2. Loosen or correct the RegExp — print the actual URL via tab.evaluate(() => location.href) and adjust the pattern.
  3. Confirm the triggering action really navigates: if the click silently failed, wait for the element and re-click before waiting.
  4. For SPA routing, verify the app updates location.href (history.pushState changes it) or poll a page-side condition with tab.waitForFunction() instead.

Example fix

// before: strict regex, times out on hash routing
await tab.waitForUrl(/^https:\/\/app\.example\.com\/dashboard$/);
// after: looser match + larger timeout
await tab.waitForUrl(/dashboard/, { timeout: 60_000 });
Defensive patterns

Strategy: retry

Validate before calling

// validate the pattern matches the current or expected URL shape
const current = await tab.evaluate(() => location.href);
if (pattern instanceof RegExp && pattern.test(current)) return current; // already there

Try / catch

try {
  await tab.waitForUrl(/dashboard/, { timeout: 30_000 });
} catch (err) {
  if (err instanceof ToolError && err.message.includes("waitForUrl() timed out")) {
    const actual = await tab.evaluate(() => location.href);
    throw new Error(`waitForUrl failed; still at ${actual}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling tab.waitForUrl(/regex/) after an action that never actually navigated (failed click, blocked popup, SPA route change that doesn't update location), a pattern that never matches the resulting URL, or navigation slower than the timeout.

Common situations: Waiting for a post-login redirect that was blocked by captcha/2FA; a RegExp written against an expected URL that the app changed (query-param or hash routing differences); SPA client-side routing that rewrites history without a full navigation the daemon observes; timeout too short for slow networks.

Understand the failure class

Related errors


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