can1357/oh-my-pi · error · ToolError
tab.waitFor(${JSON.stringify(selector)}) timed out after ${t
Error message
tab.waitFor(${JSON.stringify(selector)}) timed out after ${timeoutMs}ms What it means
`tab.waitFor(selector)` polls `#selectorExists` every 100ms until a deadline (`Date.now() + timeoutMs`); if the selector never resolves to an existing element/ref in time, it throws this ToolError naming the selector and the elapsed timeout. It is the element-wait timeout for the cmux tab facade.
Source
Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:993
})()`;
const result = (await this.#request("browser.eval", { script }, this.#runContext?.timeoutMs)) as CmuxEvalResult;
return result.value as TResult;
}
async #waitForSelector(selector: string, timeoutMs: number): Promise<void> {
const signal = this.#runContext?.signal;
const spec = this.#selectorSpec(selector);
const nativeSelector = this.#nativeSelector(spec);
if (nativeSelector) {
await this.#request("browser.wait", { selector: nativeSelector, timeout_ms: timeoutMs }, timeoutMs, signal);
return;
}
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
if (await this.#selectorExists(spec)) return;
await untilAborted(signal, () => Bun.sleep(100));
}
throw new ToolError(`tab.waitFor(${JSON.stringify(selector)}) timed out after ${timeoutMs}ms`);
}
async #selectorExists(spec: SelectorSpec): Promise<boolean> {
if (spec.kind === "ref") return this.#elementRefs.has(Number(spec.value));
const script = `(() => {
const spec = ${JSON.stringify(spec)};
${PAGE_SELECTOR_HELPERS}
return !!findElement(spec);
})()`;
return !!(await this.#evalScript<unknown>(script));
}
async #selectorBox(spec: SelectorSpec): Promise<BoundingBox | null> {
if (spec.kind === "ref") return null;
const script = `(() => {
const spec = ${JSON.stringify(spec)};
${PAGE_SELECTOR_HELPERS}
const element = findElement(spec);View on GitHub (pinned to 9690622007)
Solutions
- Increase the timeoutMs to cover the page's worst-case render time, or verify the element actually exists with tab.evaluate.
- Fix the selector: test it directly in the page (document.querySelector) and prefer stable ids/data attributes.
- Ensure any prerequisite action (login, navigation, click that triggers render) happens before waitFor.
Example fix
// before
await tab.waitFor('.results-row', { timeoutMs: 2000 });
// after
await tab.waitFor('.results-row', { timeoutMs: 15000 }); // slow API renders in ~5-8s Defensive patterns
Strategy: retry
Validate before calling
const exists = await tab.evaluate(`!!document.querySelector(${JSON.stringify(sel)})`);
if (!exists) console.warn('waitFor: selector not in DOM yet:', sel); Try / catch
try {
await tab.waitFor(sel, { timeoutMs: 15000 });
} catch (err) {
if (String(err.message).includes('timed out after')) {
// retry once with longer budget after checking page state
await tab.waitFor(sel, { timeoutMs: 30000 });
} else throw err;
} Prevention
- Budget timeouts for worst-case render time, not happy-path.
- Perform prerequisite actions (login, navigate, trigger) before waitFor.
- Prefer stable selectors (ids, data-test attributes) and verify them in DevTools first.
When it happens
Trigger: Waiting for an element that never appears: wrong selector, element rendered only after an action you didn't perform, SPA route not navigated, or a timeoutMs shorter than the page's actual render time.
Common situations: Slow network or API causing late render; selector written for a different page version; waiting for content behind login/auth that redirected away; using a CSS selector where the spec expects a different kind (text/ref).
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- ${label} failed fast after ${afterMs}ms${formatSelectorMatch
- tab.select() requires a <select> element
- Drag selector did not resolve to a visible element: ${target
- Timed out clicking ${selector} (seen ${lastSeen} matches; la
- tab.ariaSnapshot: selector ${JSON.stringify(selector)} match
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/2dc96af061a105bf.
Report an issue: GitHub.