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

  1. Increase the timeoutMs to cover the page's worst-case render time, or verify the element actually exists with tab.evaluate.
  2. Fix the selector: test it directly in the page (document.querySelector) and prefer stable ids/data attributes.
  3. 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

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

Related errors


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