can1357/oh-my-pi · error · ToolError
Timed out clicking ${selector} (seen ${lastSeen} matches; la
Error message
Timed out clicking ${selector} (seen ${lastSeen} matches; last reason: ${lastReason ?? "unknown"}). If there are multiple matching elements, use observe + tab.id() or a more specific selector. What it means
The click loop resolves candidate handles for the selector and retries until an overall timeout; each attempt records why the click was skipped. If the loop exhausts the timeout without a successful click, this error reports how many matches were seen and the last skip reason, and suggests observe + tab.id() or a more specific selector.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:888
}
const actionability = await isClickActionable(target);
if (!actionability.ok) {
lastReason = actionability.reason;
await untilAborted(clickSignal, () => Bun.sleep(100));
continue;
}
try {
await untilAborted(clickSignal, () => target.click());
return;
} catch (err) {
lastReason = err instanceof Error ? err.message : String(err);
await untilAborted(clickSignal, () => Bun.sleep(100));
}
} finally {
await Promise.all(handles.map(async handle => handle.dispose().catch(() => undefined)));
}
}
throw new ToolError(
`Timed out clicking ${selector} (seen ${lastSeen} matches; last reason: ${lastReason ?? "unknown"}). ` +
"If there are multiple matching elements, use observe + tab.id() or a more specific selector.",
);
}
/**
* Hint appended to a selector op's fail-fast timeout, given the selector's current
* match count: a missing element (consent wall, wrong page) reads differently from
* a present-but-unactionable one.
*/
export function formatSelectorMatchHint(count: number): string {
return count === 0
? "; selector currently matches no elements — run tab.observe() or tab.ariaSnapshot() to inspect the page"
: `; selector currently matches ${count} element(s) but the action never became possible — the element may be hidden or covered (try tab.scrollIntoView() or a more specific selector)`;
}
export interface InflightOp {
label: string;View on GitHub (pinned to 9690622007)
Solutions
- Read the `last reason` in the message (e.g. not visible, intercepted, detached) and address that specific condition.
- Use a more specific selector: prefer `aria/<name>` or `text/<exact label>` over broad CSS.
- Run `tab.observe()` and click by handle id (`tab.id()`) to disambiguate multiple matches.
- Wait for the element to be actionable before clicking, or increase the action timeout.
Example fix
// before
tab.click("text/Submit"); // matches 3 buttons, none clickable
// after
const obs = await tab.observe();
const btn = obs.find(el => el.name === "Submit order");
tab.click(`aria/${btn.name}`); Defensive patterns
Strategy: validation
Validate before calling
const matches = await tab.handles(selector);
if (matches.length > 1) {
const obs = await tab.observe();
throw new Error(`selector '${selector}' matched ${matches.length} elements; pick one via observe`);
} Try / catch
try {
await tab.click(selector);
} catch (err) {
if (err instanceof ToolError && err.message.startsWith("Timed out clicking")) {
const obs = await tab.observe();
const target = obs.find(el => el.clickable && el.name === expectedLabel);
await tab.click(target.id);
} else throw err;
} Prevention
- Prefer aria/<exact name> over broad text/CSS selectors
- Check the 'last reason' in the timeout message before retrying blindly
- Disambiguate multi-match selectors via observe + tab.id()
When it happens
Trigger: Selector matching multiple elements where earlier matches are non-clickable (hidden, covered, disabled, detached) so every attempt is skipped; selector matching zero elements; element becomes visible only after the timeout.
Common situations: Cookie banners with several similarly-named buttons; elements behind overlays; SPA where the target element mounts slowly; ambiguous selectors like `text/OK` matching many buttons.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- tab.waitFor(${JSON.stringify(selector)}) timed out after ${t
- Playwright-only selector ${JSON.stringify(selector)} is not
- Unsupported selector prefix. Use CSS or puppeteer query hand
- ${label} cannot run: this handle was invalidated after ${sta
- ${label} failed fast after ${afterMs}ms${formatSelectorMatch
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f45ae44434fe4ddb.
Report an issue: GitHub.