{"record":{"id":"99eb9fb78bbe5625","repo":"can1357/oh-my-pi","slug":"tab-waitforurl-timed-out-after-timeoutms-ms","errorCode":null,"errorMessage":"tab.waitForUrl() timed out after ${timeoutMs}ms","messagePattern":"tab\\.waitForUrl\\(\\) timed out after (.+?)ms","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts","lineNumber":645,"sourceCode":"\t\t\t\tthis.#lastUrl = result.url;\n\t\t\t}\n\t\t\treturn this.#lastUrl;\n\t\t}\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\twhile (Date.now() <= deadline) {\n\t\t\tconst result = (await this.#request(\n\t\t\t\t\"browser.url.get\",\n\t\t\t\t{},\n\t\t\t\tMath.min(timeoutMs, 5_000),\n\t\t\t\tsignal,\n\t\t\t)) as CmuxUrlGetResult;\n\t\t\tif (typeof result.url === \"string\" && result.url.length > 0) {\n\t\t\t\tthis.#lastUrl = result.url;\n\t\t\t\tif (pattern.test(result.url)) return result.url;\n\t\t\t}\n\t\t\tawait untilAborted(signal, () => Bun.sleep(200));\n\t\t}\n\t\tthrow new ToolError(`tab.waitForUrl() timed out after ${timeoutMs}ms`);\n\t}\n\n\tasync waitForNavigation(opts?: { waitUntil?: WaitUntil; timeout?: number }): Promise<null> {\n\t\tconst timeoutMs = opts?.timeout ?? this.#runContext?.timeoutMs ?? 30_000;\n\t\tconst signal = this.#runContext?.signal;\n\t\t// Cmux has no native \"next navigation\" wait — snapshot the current URL via a fresh\n\t\t// `browser.url.get` (never the possibly-stale `#lastUrl`), then poll for a change\n\t\t// from it (mirroring headless `page.waitForNavigation` intent) and optionally settle\n\t\t// on the requested load state. Start it BEFORE the click/submit that navigates; after\n\t\t// a completed nav it times out like puppeteer does.\n\t\tconst baseline = (await this.#request(\n\t\t\t\"browser.url.get\",\n\t\t\t{},\n\t\t\tMath.min(timeoutMs, 5_000),\n\t\t\tsignal,\n\t\t)) as CmuxUrlGetResult;\n\t\tconst startUrl = typeof baseline.url === \"string\" && baseline.url.length > 0 ? baseline.url : this.#lastUrl;\n\t\tif (typeof baseline.url === \"string\" && baseline.url.length > 0) this.#lastUrl = baseline.url;","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts#L627-L663","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Increase opts.timeout (e.g. tab.waitForUrl(/dashboard/, { timeout: 60000 })).","Loosen or correct the RegExp — print the actual URL via tab.evaluate(() => location.href) and adjust the pattern.","Confirm the triggering action really navigates: if the click silently failed, wait for the element and re-click before waiting.","For SPA routing, verify the app updates location.href (history.pushState changes it) or poll a page-side condition with tab.waitForFunction() instead."],"exampleFix":"// before: strict regex, times out on hash routing\nawait tab.waitForUrl(/^https:\\/\\/app\\.example\\.com\\/dashboard$/);\n// after: looser match + larger timeout\nawait tab.waitForUrl(/dashboard/, { timeout: 60_000 });","handlingStrategy":"retry","validationCode":"// validate the pattern matches the current or expected URL shape\nconst current = await tab.evaluate(() => location.href);\nif (pattern instanceof RegExp && pattern.test(current)) return current; // already there","typeGuard":null,"tryCatchPattern":"try {\n  await tab.waitForUrl(/dashboard/, { timeout: 30_000 });\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"waitForUrl() timed out\")) {\n    const actual = await tab.evaluate(() => location.href);\n    throw new Error(`waitForUrl failed; still at ${actual}`);\n  }\n  throw err;\n}","preventionTips":["Test your RegExp against the real URL (log location.href) before relying on it","Pass an explicit timeout sized for the slowest expected navigation","For SPAs, confirm the route updates location or poll a DOM condition instead","Ensure the triggering action (click/submit) actually succeeded before waiting"],"tags":["browser","timeout","navigation","polling"],"backgroundTag":"wait-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}