can1357/oh-my-pi · error · ToolError
page.waitForFunction() timed out after ${timeoutMs}ms
Error message
page.waitForFunction() timed out after ${timeoutMs}ms What it means
CmuxTab.waitForFunction(fn, args, opts) evaluates fn in the page every pollingMs until it returns a truthy value, and throws this ToolError when the deadline (timeoutMs or default) expires first. It mirrors Puppeteer's page.waitForFunction semantics: the predicate never became truthy in time.
Source
Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:842
const result = await this.#captureScreenshotPng(this.#runContext?.timeoutMs ?? 30_000);
return opts.encoding === "base64" ? result.png_base64 : Buffer.from(result.png_base64, "base64");
}
async waitForFunction(
fn: string | ((...args: unknown[]) => unknown | Promise<unknown>),
opts: { timeout?: number; polling?: number } | undefined,
...args: unknown[]
): Promise<unknown> {
const timeoutMs = opts?.timeout ?? this.#runContext?.timeoutMs ?? 30_000;
const signal = this.#runContext?.signal;
const pollingMs = typeof opts?.polling === "number" ? opts.polling : 200;
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
const value = typeof fn === "string" ? await this.#evalScript<unknown>(fn) : await this.evaluate(fn, ...args);
if (value) return value;
await untilAborted(signal, () => Bun.sleep(pollingMs));
}
throw new ToolError(`page.waitForFunction() timed out after ${timeoutMs}ms`);
}
async #evalScript<TResult>(script: string, timeoutMs?: number): Promise<TResult> {
const result = (await this.#request("browser.eval", { script }, timeoutMs)) as CmuxEvalResult;
return result.value as TResult;
}
async #captureScreenshotPng(timeoutMs: number): Promise<CmuxScreenshotResult & { png_base64: string }> {
const result = (await this.#request("browser.screenshot", {}, timeoutMs)) as CmuxScreenshotResult;
if (typeof result.png_base64 !== "string" || result.png_base64.length === 0) {
throw new ToolError("cmux browser screenshot response did not include png_base64");
}
return result as CmuxScreenshotResult & { png_base64: string };
}
async #selectorAction<TResult = void>(
selector: string,
action: string,View on GitHub (pinned to 9690622007)
Solutions
- Increase the timeout option and confirm pollingMs suits the condition (default poll interval may miss slow updates only if the deadline is the issue).
- Debug the predicate: run it once via tab.evaluate(fn) and inspect the actual value/error instead of guessing.
- Make the predicate defensive — return false instead of throwing when the target doesn't exist yet (e.g. !!document.querySelector(...) style checks).
- Ensure the precondition holds (trigger the action, load the data, or scroll) so the condition can ever become truthy.
Example fix
// before: predicate throws while lib not yet loaded
await tab.waitForFunction(() => window.chart.getData().length > 0, undefined, { timeout: 5000 });
// after: defensive predicate, longer timeout
await tab.waitForFunction(
() => !!window.chart && window.chart.getData().length > 0,
undefined,
{ timeout: 30_000 },
); Defensive patterns
Strategy: try-catch
Validate before calling
// run the predicate once up front to see its real value/error
const initial = await tab.evaluate(() => !!document.querySelector(".result-row"));
if (!initial) console.warn("predicate currently false; waitForFunction will poll"); Try / catch
try {
await tab.waitForFunction(() => document.querySelectorAll(".row").length > 0, undefined, { timeout: 30_000 });
} catch (err) {
if (err instanceof ToolError && err.message.includes("waitForFunction() timed out")) {
const debug = await tab.evaluate(() => ({ rows: document.querySelectorAll(".row").length }));
throw new Error(`condition never became true: ${JSON.stringify(debug)}`);
}
throw err;
} Prevention
- Write predicates that return false rather than throw when prerequisites are missing
- Reference only page-side globals — predicates run in the browser, not Node
- Run the predicate once via tab.evaluate to validate it before polling
- Set timeouts from measured page-load behavior, not guesses
When it happens
Trigger: The polled condition never becomes true — element never appears, async data never loads, or fn throws/returns falsy on every poll; pollingMs/timeout misconfigured (e.g. timeout 0 or too small); fn references page globals that don't exist (evaluated in page context, not Node).
Common situations: Waiting for an element the app renders only under conditions that never occurred (feature flag off, backend error); polling a value that requires scrolling to trigger lazy load; predicates referencing Node-side variables instead of page-side ones; fn silently throwing each iteration because a library isn't loaded yet.
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
- tab.waitForResponse() timed out after ${timeoutMs}ms
- xAI device-code token polling failed: ${error instanceof Err
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- tab.waitForNavigation() timed out after ${timeoutMs}ms
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b3e82301c20a2675.
Report an issue: GitHub.