can1357/oh-my-pi · error · ToolError
tab.waitForNavigation() timed out after ${timeoutMs}ms
Error message
tab.waitForNavigation() timed out after ${timeoutMs}ms What it means
CmuxTab.waitForNavigation() snapshots the current URL and polls browser.url.get every 200ms until it differs from the baseline, throwing this ToolError when the timeout expires without any URL change. Because cmux has no native 'next navigation' wait, the URL must actually change — same-URL navigations or calls made after the navigation completed will time out.
Source
Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:688
signal,
)) as CmuxUrlGetResult;
if (typeof result.url === "string" && result.url.length > 0) {
this.#lastUrl = result.url;
if (result.url !== startUrl) {
if (opts?.waitUntil) {
await this.#request(
"browser.wait",
{ load_state: mapWaitUntil(opts.waitUntil), timeout_ms: timeoutMs },
timeoutMs,
signal,
);
}
return null;
}
}
await untilAborted(signal, () => Bun.sleep(200));
}
throw new ToolError(`tab.waitForNavigation() timed out after ${timeoutMs}ms`);
}
async drag(from: DragTarget, to: DragTarget): Promise<void> {
const start = await this.#dragPoint(from);
const end = await this.#dragPoint(to);
await this.#evalScript(
`(() => {
const points = ${JSON.stringify({ start, end })};
const target = document.elementFromPoint(points.start.x, points.start.y) || document.body;
const dispatch = (type, point) => target.dispatchEvent(new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
clientX: point.x,
clientY: point.y,
buttons: type === "mouseup" ? 0 : 1,
}));
dispatch("mousemove", points.start);View on GitHub (pinned to 9690622007)
Solutions
- Start waitForNavigation() BEFORE the click/submit that triggers navigation, then await the click.
- Increase opts.timeout for slow pages.
- If the URL does not change (same-URL nav or SPA state change), replace with tab.waitForFunction() polling a DOM condition or tab.waitForSelector() on a post-nav element.
- Verify the click actually fired: use tab.waitFor(selector) first, scroll the element into view, and check that no overlay intercepts the pointer events.
Example fix
// before: wait after the click already completed
await tab.click("#submit");
await tab.waitForNavigation();
// after: arm the wait first, then act
const nav = tab.waitForNavigation({ timeout: 60_000 });
await tab.click("#submit");
await nav; Defensive patterns
Strategy: try-catch
Validate before calling
// only meaningful if the URL can actually change const before = await tab.evaluate(() => location.href); // if your flow cannot change the URL, prefer waitForFunction instead
Try / catch
const nav = tab.waitForNavigation({ timeout: 30_000 });
await tab.click("#submit");
try {
await nav;
} catch (err) {
if (err instanceof ToolError && err.message.includes("waitForNavigation() timed out")) {
await tab.waitForSelector("[data-loaded='true']", { timeout: 30_000 }); // same-URL fallback
} else throw err;
} Prevention
- Always arm waitForNavigation BEFORE the triggering action
- Use waitForSelector/waitForFunction for same-URL or SPA-only transitions
- Set explicit timeouts appropriate to network conditions
- Verify the click target is visible and not covered by overlays
When it happens
Trigger: Calling waitForNavigation() after the navigation already finished (baseline taken post-nav, URL never changes again); the triggering click/submit failed or was intercepted; the page navigates to the same URL (e.g. form POST back to itself) so the URL comparison never differs; timeout too short for slow loads.
Common situations: Puppeteer-style code that clicks and waits, but the click was swallowed by an overlay or new-tab popup (cmux may not follow target=_blank); SPA route transitions that keep the same URL string; polling during a network stall; forgetting that waitForNavigation must be started BEFORE the action per the documented contract.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- tab.waitForUrl() timed out after ${timeoutMs}ms
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- tab.waitForResponse() timed out after ${timeoutMs}ms
- page.waitForFunction() timed out after ${timeoutMs}ms
- autoStarted ? `omp browser relay is serving at ${cdpUrl} but
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d974438237d5073e.
Report an issue: GitHub.