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

  1. Read resolution.code, hint and candidates in the TargetError — the candidates array lists near-misses to correct your selector against.
  2. Re-take the element ref/snapshot from the current page state before retrying; stale refs are the most common cause.
  3. Add a wait for the element to appear (waitForSelector or the library's auto-wait options) before resolving.
  4. Narrow or broaden the selector: use a more specific CSS/XPath for ambiguity, or a text/role selector for brittle class-name-based ones.
  5. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/e39cac9974c3c9de. Report an issue: GitHub.