jackwener/OpenCLI · error · Error

No iframe target found for frame

Error message

No iframe target found for frame ${frameId}${targetUrl ? ` (${targetUrl})` : ''}. Candidates: ${candidates || 'none'}

What it means

This error is thrown by resolveFrameTargetId in extension/src/cdp.ts:587 when ensureFrameTarget cannot find a CDP iframe target matching the requested frameId (or targetUrl) among the tab's Target.getTargets() results. The extension auto-attaches to iframes via Target.setAutoAttach with an iframe filter, then looks up the matching out-of-process iframe target to route frame-scoped CDP commands. If no candidate matches, it throws and includes all discovered iframe candidates in the message to aid debugging.

Solutions

  1. Pass the frame's exact URL as targetUrl so resolveFrameTargetId can match by URL, or re-resolve the frameId from Page.getFrameTree immediately before the call.
  2. Retry after a short delay to let Target.setAutoAttach register the iframe target (this is what aggressiveRetry is for).
  3. Verify with Target.getTargets (or the 'none' in the message) whether any iframe targets exist; if none, evaluate in the main tab context instead.
  4. If the call came from evaluateInFrame, ensure Runtime.enable was issued so execution-context tracking populated tabFrameContexts; clear stale context cache and retry.
  5. Check Chrome version/platform — some embedders (e.g. Electron, older Chromium) do not create OOPIF targets for same-site iframes.

Example fix

// before
await sendCommandInFrameTarget(tabId, frameId, 'Runtime.evaluate', { expression });
// after
const tree = await getFrameTree(tabId);
const frameUrl = tree?.frame?.childFrames?.find(f => f.frame.id === frameId)?.frame.url;
await sendCommandInFrameTarget(tabId, frameId, 'Runtime.evaluate', { expression }, true /* aggressiveRetry */, undefined, frameUrl);
Defensive patterns

Strategy: retry

Validate before calling

const tree = await cdp.getFrameTree(tabId);
const hasIframeTargets = (await cdp.getTargetInfos(tabId)).some(t => t.type === 'iframe');
if (!hasIframeTargets) throw new SkipError('page has no OOPIF iframe targets; evaluate in main frame');

Type guard

function isIframeTarget(t: { type?: string; targetId?: string; id?: string }): t is { type: 'iframe'; targetId: string } {
  return t.type === 'iframe' && typeof (t.targetId || t.id) === 'string';
}

Try / catch

try {
  await sendCommandInFrameTarget(tabId, frameId, method, params, true /* aggressiveRetry */, timeoutMs, frameUrl);
} catch (err) {
  if (err instanceof Error && /No iframe target found/.test(err.message)) {
    await sleep(250); // let Target.setAutoAttach register the OOPIF target
    return sendCommandInFrameTarget(tabId, frameId, method, params, true, timeoutMs, frameUrl);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling sendCommandInFrameTarget or evaluateInFrame with a frameId that has no matching out-of-process iframe target: the frame is same-process (no separate target created), the frame navigated/reloaded and its target id changed, Target.getTargets returned before the iframe target was registered, the frameId passed is a Chrome frameTree frameId rather than a CDP iframe targetId, or the page has no iframes at all.

Common situations: Automating a page whose iframes are same-origin (Chrome keeps them in-process, so no iframe target exists); racing a just-inserted iframe before auto-attach fires; stale cached frame references after SPA navigation; passing the wrong id field (e.g. webFrameTree frameId vs targetId); headless or restricted environments where Target.setDiscoverTargets fails silently.

Related errors


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

Appendix: source

Thrown at extension/src/cdp.ts:587

  const result = await sendDebuggerCommand({ tabId }, 'Target.getTargets').catch(() => null) as
    | { targetInfos?: Array<{ targetId?: string; id?: string; type?: string; url?: string }> }
    | null;
  const targets = result?.targetInfos ?? [];
  const frameTarget = targets.find((candidate) => {
    const candidateId = candidate.targetId || candidate.id;
    return candidate.type === 'iframe'
      && (
        candidateId === frameId
        || (!!targetUrl && candidate.url === targetUrl)
      );
  });
  const targetId = frameTarget?.targetId || frameTarget?.id;
  if (targetId) return targetId;
  const candidates = targets
    .filter((target) => target.type === 'iframe')
    .map((target) => `${target.targetId || target.id || '?'} ${target.url || ''}`)
    .join('; ');
  throw new Error(`No iframe target found for frame ${frameId}${targetUrl ? ` (${targetUrl})` : ''}. Candidates: ${candidates || 'none'}`);
}

export async function sendCommandInFrameTarget(
  tabId: number,
  frameId: string,
  method: string,
  params: Record<string, unknown> = {},
  aggressiveRetry: boolean = false,
  timeoutMs: number = CDP_COMMAND_TIMEOUT_MS,
  targetUrl?: string,
): Promise<unknown> {
  const targetId = await ensureFrameTarget(tabId, frameId, aggressiveRetry, targetUrl);
  const target = { targetId } as chrome.debugger.Debuggee;
  return sendDebuggerCommand(target, method, params, timeoutMs);
}

export async function insertText(
  tabId: number,

View on GitHub (pinned to 49907e53dc)