can1357/oh-my-pi · error · ToolError

wait(predicate) timed out after ${timeout}ms — predicate nev

Error message

wait(predicate) timed out after ${timeout}ms — predicate never returned truthy

What it means

waitForRun polls the predicate on an interval until it returns a truthy value or a deadline (opts.timeout, default DEFAULT_PREDICATE_TIMEOUT_MS) expires. When the predicate stays falsy past the deadline, it throws this ToolError naming the timeout in milliseconds.

Source

Thrown at packages/coding-agent/src/tools/run-scope.ts:362

			await untilAborted(signal, async () => await Bun.sleep(msOrPredicate));
			throwIfAborted(signal);
			return undefined;
		}
		if (typeof msOrPredicate !== "function") {
			throw new ToolError("wait(...) expects milliseconds (number) or a predicate function to poll");
		}
		const timeout =
			opts?.timeout !== undefined && Number.isFinite(opts.timeout) && opts.timeout > 0
				? opts.timeout
				: DEFAULT_PREDICATE_TIMEOUT_MS;
		const interval = Math.max(opts?.interval ?? 100, 10);
		const deadline = Date.now() + timeout;
		for (;;) {
			const value = await untilAborted(signal, async () => await msOrPredicate());
			throwIfAborted(signal);
			if (value) return value;
			if (Date.now() + interval > deadline) {
				throw new ToolError(`wait(predicate) timed out after ${timeout}ms — predicate never returned truthy`);
			}
			await untilAborted(signal, async () => await Bun.sleep(interval));
		}
	})();
	return trackBrowserRunPromise(promise);
}

/** Binds a long-lived scope facade (page/tab/desktop objects) to one evaluated run's abort signal. */
export function bindRunFacade<T extends object>(
	target: T,
	signal: AbortSignal,
	rejectionOwner?: object,
	onFloatingRejection?: FloatingRejectionHandler,
): T {
	const cache = new Map<PropertyKey, unknown>();
	return new Proxy(target, {
		get(current, prop) {
			throwIfAborted(signal);

View on GitHub (pinned to 9690622007)

Solutions

  1. Increase opts.timeout (e.g. wait(pred, { timeout: 60000 }))
  2. Fix the predicate so it checks the correct field/value
  3. Verify the underlying run actually progresses or completes; if it hangs, kill/restart it
  4. Tune opts.interval if polling overhead matters

Example fix

// before
await wait(() => run.status === "complete"); // times out on slow run
// after
await wait(() => run.status === "complete", { timeout: 120000 });
Defensive patterns

Strategy: retry

Validate before calling

// ensure the waited-for condition is achievable before polling
if (!run || run.finished && !expectedCondition(run)) throw new Error("Run ended without reaching the awaited condition");

Try / catch

try { await wait(pred); } catch (e) { if (e instanceof ToolError && e.message.includes("timed out after")) { await wait(pred, { timeout: e_TIMEOUT * 2 }); } else throw e; }

Prevention

When it happens

Trigger: wait(predicate) where the predicate never becomes truthy within the timeout — condition never met (e.g. waiting for output text that never appears), timeout too short for a slow run, or predicate checking the wrong field.

Common situations: Waiting on a long-running browser/run command whose completion exceeds the default predicate timeout; predicate compares against output that got truncated; status field name typo means it never matches.

Understand the failure class

Related errors


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