jackwener/OpenCLI · error · Error
No iframe target found for frame ${frameId}${targetUrl ? ` (
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.
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)
Solutions
- 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.
- Retry after a short delay to let Target.setAutoAttach register the iframe target (this is what aggressiveRetry is for).
- Verify with Target.getTargets (or the 'none' in the message) whether any iframe targets exist; if none, evaluate in the main tab context instead.
- If the call came from evaluateInFrame, ensure Runtime.enable was issued so execution-context tracking populated tabFrameContexts; clear stale context cache and retry.
- 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
- Always pass the frame's current URL (from Page.getFrameTree) as targetUrl to enable URL-based matching.
- Use aggressiveRetry for freshly created/navigated iframes so auto-attach has time to register the target.
- Distinguish frameTree frameIds from CDP iframe targetIds; don't pass one where the other is expected.
- Log the Candidates list from the error to detect systematic absence of OOPIF targets (same-process iframes).
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
- Bound tab for session "${session.session}" is not debuggable
- bound_tab_not_debuggable
- Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}
- Tab ${tabId} no longer exists
- attach failed: ${lastError}${hint}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/c8bcfbb26926c1b4.
Report an issue: GitHub.