jackwener/OpenCLI · error · TargetError
${resolution.code}
${resolution.code}
Error message
${resolution.message} What it means
runResolve (src/browser/base-page.ts:124) executes a target-resolution script in the page via page.evaluate and expects a structured result. When the in-page resolver returns {ok:false}, the failure (code, message, hint, optional candidate list, match count) is re-thrown as a TargetError. This is the library's normal way of reporting 'your selector/target reference did not resolve to an element', not an unexpected crash.
Source
Thrown at src/browser/base-page.ts:124
frame?: { id?: string; url?: string; unreachableUrl?: string; name?: string };
childFrames?: CdpFrameTreeNode[];
}
/**
* Execute `resolveTargetJs` once, throw structured `TargetError` on failure.
* Single helper so click/typeText/scrollTo share one resolution pathway,
* which is what the selector-first contract promises agents.
*/
async function runResolve(
page: { evaluate(js: string): Promise<unknown> },
ref: string,
opts: ResolveOptions = {},
): Promise<ResolveSuccess> {
const resolution = (await page.evaluate(resolveTargetJs(ref, opts))) as
| { ok: true; matches_n: number; match_level: TargetMatchLevel }
| { ok: false; code: TargetErrorCode; message: string; hint: string; candidates?: string[]; matches_n?: number };
if (!resolution.ok) {
throw new TargetError({
code: resolution.code,
message: resolution.message,
hint: resolution.hint,
candidates: resolution.candidates,
matches_n: resolution.matches_n,
});
}
return { matches_n: resolution.matches_n, match_level: resolution.match_level };
}
function previewText(text: string | undefined): string | undefined {
const preview = (text ?? '').replace(/\s+/g, ' ').trim().slice(0, 300);
return preview ? `Response preview: ${preview}` : undefined;
}
function parseKeyChord(rawKey: string): { key: string; modifiers: string[] } {
const parts = rawKey.split('+').map(part => part.trim()).filter(Boolean);
if (parts.length <= 1) return { key: rawKey, modifiers: [] };View on GitHub (pinned to 49907e53dc)
Solutions
- Read resolution.code, hint and candidates in the TargetError — the candidates array lists near-misses to correct your selector against.
- Re-take the element ref/snapshot from the current page state before retrying; stale refs are the most common cause.
- Add a wait for the element to appear (waitForSelector or the library's auto-wait options) before resolving.
- Narrow or broaden the selector: use a more specific CSS/XPath for ambiguity, or a text/role selector for brittle class-name-based ones.
- If the element is in an iframe, pass the appropriate frame option to the resolver.
Example fix
// before
const el = await runResolve(page, { ref: 'e12' }); // stale snapshot ref
// after
await page.waitForSelector('[data-testid="checkout-btn"]', { timeout: 5000 });
const el = await runResolve(page, { ref: await takeFreshSnapshot(page), opts: { timeout: 5000 } }); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check the selector before resolving:
const count = await page.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
if (count === 0) throw new SkipError(`selector matches nothing: ${selector}`); Type guard
function isTargetError(err: unknown): err is TargetError {
return err instanceof TargetError || (typeof err === 'object' && err !== null && 'code' in err && 'hint' in err);
} Try / catch
try {
return await runResolve(page, ref, opts);
} catch (err) {
if (isTargetError(err)) {
console.warn(`resolve failed [${err.code}]: ${err.hint}`, err.candidates);
await page.waitForTimeout(1000);
return runResolve(page, ref, opts); // retry once after wait
}
throw err;
} Prevention
- Always re-take element refs from a fresh snapshot; refs are invalid after DOM re-renders.
- Read err.code and err.candidates — they tell you exactly why the selector failed and what was near.
- Add explicit waits for dynamic content before resolving.
- Prefer resilient selectors (data-testid, text, role) over generated class names.
When it happens
Trigger: Resolving an element target whose CSS/XPath/text/role selector matches zero elements (code like not-found), matches ambiguously, or whose ref/frame the resolver can't locate in the current DOM — e.g. the page changed since the snapshot that produced the ref.
Common situations: DOM updated by a re-render or framework hydration so refs from a previous snapshot no longer exist; typos in selectors; targeting elements inside iframes without proper frame options; dynamic content not yet rendered when resolution runs; strict-mode ambiguity with several matches.
Related errors
- Could not find Antigravity input box
- Could not find input box
- Could not find antigravity.agentSidePanelInputBox
- Could not find Antigravity input box
- Could not locate Midjourney ${slotLabel} slot after upload
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/e39cac9974c3c9de.
Report an issue: GitHub.