can1357/oh-my-pi · error · ToolError
${label} failed fast after ${afterMs}ms${formatSelectorMatch
Error message
${label} failed fast after ${afterMs}ms${formatSelectorMatchHint(0)} What it means
A wait-for-disappearance style poll confirmed the selector matches exactly 0 elements and, after reaching the deadline with zero matches still true, fails fast with this ToolError including elapsed ms and a selector match hint. It distinguishes 'element never appeared' from 'element did not disappear in time'.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:1523
* matches; an inconclusive probe (mid-navigation, detached frame) never counts
* toward the zero-match window.
*/
async #zeroMatchWatchdog(selector: string, label: string, afterMs: number, signal: AbortSignal): Promise<never> {
const page = this.#requirePage();
const resolved = normalizeSelector(selector);
const deadline = Date.now() + afterMs;
while (!signal.aborted) {
let count: number | null = null;
try {
const handles = await page.$$(resolved);
count = handles.length;
for (const handle of handles) void handle.dispose().catch(() => undefined);
} catch {
// Inconclusive probe — keep polling without advancing toward failure.
}
if (count !== null && count > 0) break;
if (count === 0 && Date.now() >= deadline) {
throw new ToolError(`${label} failed fast after ${afterMs}ms${formatSelectorMatchHint(0)}`);
}
try {
await untilAborted(signal, () => Bun.sleep(ZERO_MATCH_POLL_MS));
} catch {
break;
}
}
return await new Promise<never>(() => {});
}
/**
* Best-effort match-count probe for a timed-out selector op. Never throws;
* empty string when the probe fails, stalls, or the selector is an aria-ref.
*/
async #selectorTimeoutHint(selector: string): Promise<string> {
if (parseAriaRefSelector(selector) !== null) return "";
try {
const handles = await Promise.race([View on GitHub (pinned to 9690622007)
Solutions
- Verify the selector actually matches elements in the intended frame (query it directly with page.$)
- Check whether the element ever existed — if it should appear first, wait for appearance before waiting for disappearance
- Use ariaSnapshot to inspect the current DOM and correct the selector
- Increase the timeout only after confirming the element is present and the disappearance is genuinely pending
Example fix
// before
await tab.waitForSelectorGone('.spinner', { timeout: 3000 });
// after
if (await tab.$('.spinner')) {
await tab.waitForSelectorGone('.spinner', { timeout: 10000 });
} Defensive patterns
Strategy: validation
Validate before calling
const present = await tab.$(selector);
if (!present) throw new Error(`selector ${selector} never matched — fix it before waiting for disappearance`); Try / catch
try {
await waitForGone(selector, { timeout: ms });
} catch (err) {
if (err instanceof ToolError && /failed fast/.test(err.message)) {
// selector matched nothing from the start: treat as selector bug
const snap = await tab.ariaSnapshot();
logger.warn('selector never matched', { selector, snap });
} else throw err;
} Prevention
- Confirm the element exists before waiting for it to disappear
- Test selectors with page.$ or a full ariaSnapshot first
- Scope selectors to the correct frame/shadow root
- Distinguish 'never matched' from 'timed out waiting' when choosing timeouts
When it happens
Trigger: Waiting for an element/selector to disappear (count must reach 0) where the first probe already returned count === 0 and the fast-fail deadline (afterMs) elapsed, so the condition was satisfied-but-never-pending — typically a wrong selector that never matched anything.
Common situations: Selector typo or wrong frame/shadow-root scope so it never matches; asserting a spinner disappears when the page never rendered it; CSS/XPath mismatch after a DOM redesign.
Related errors
- tab.waitFor(${JSON.stringify(selector)}) timed out after ${t
- 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/3b6a24185abaf0c8.
Report an issue: GitHub.