jackwener/OpenCLI · error · Error
Page not found: ${targetId} — stale page identity
Error message
Page not found: ${targetId} — stale page identity What it means
resolveTabId (extension/src/identity.ts:44) performs the reverse mapping: CDP targetId → internal tabId. After a cache miss it refreshes from chrome.debugger.getTargets() and throws if the targetId isn't found, deliberately never guessing. The targetId is stale — the page it identified no longer exists (closed, navigated to a new target, or the browser restarted).
Source
Thrown at extension/src/identity.ts:44
const result = tabToTarget.get(tabId);
if (!result) throw new Error(`No targetId for tab ${tabId} — page may have been closed`);
return result;
}
/**
* Resolve tabId for a given targetId.
* Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets().
* Throws if no tabId can be found — never falls back to guessing.
*/
export async function resolveTabId(targetId: string): Promise<number> {
const cached = targetToTab.get(targetId);
if (cached !== undefined) return cached;
await refreshMappings();
const result = targetToTab.get(targetId);
if (result === undefined) throw new Error(`Page not found: ${targetId} — stale page identity`);
return result;
}
/**
* Remove mappings for a closed tab.
* Called from chrome.tabs.onRemoved listener.
*/
export function evictTab(tabId: number): void {
const targetId = tabToTarget.get(tabId);
if (targetId) targetToTab.delete(targetId);
tabToTarget.delete(tabId);
}
/**
* Full refresh of targetId ↔ tabId mappings from chrome.debugger.getTargets().
*/
async function refreshMappings(): Promise<void> {
const targets = await chrome.debugger.getTargets();View on GitHub (pinned to 49907e53dc)
Solutions
- Catch the error and re-resolve the page identity from a durable key (URL or your own session id) instead of the stale targetId.
- Validate the targetId with chrome.debugger.getTargets() (or chrome.tabs.query) before calling, and remove it from your state if absent.
- If the page was navigated, re-acquire the target via the tab's current target rather than reusing the old UUID.
- Persist URLs, not targetIds, for anything surviving a browser restart.
- Ensure you're not passing an iframe or worker targetId — only 'page' targets are mapped.
Example fix
// before const tabId = await resolveTabId(targetId); // after const exists = (await chrome.debugger.getTargets()).some(t => t.id === targetId && t.type === 'page'); const tabId = exists ? await resolveTabId(targetId) : await reopenPageAndResolve(savedUrl);
Defensive patterns
Strategy: try-catch
Validate before calling
const known = (await chrome.debugger.getTargets()).some(t => t.id === targetId && t.type === 'page'); if (!known) throw new StalePageIdentityError(targetId);
Try / catch
try {
tabId = await resolveTabId(targetId);
} catch (err) {
if (err instanceof Error && /Page not found/.test(err.message)) {
return recoverPageByKey(pageKey); // re-acquire via URL/session key, never reuse the stale targetId
}
throw err;
} Prevention
- Treat targetIds as ephemeral: persist URLs or your own page keys, never UUIDs, across sessions.
- Evict targetIds from local caches when Target.detachedFromTarget or tabs.onRemoved fires.
- Never pass iframe/worker target ids to page-identity APIs — only 'page' targets are mapped.
- Re-resolve identity at the start of each automation step rather than caching it for the whole run.
When it happens
Trigger: Calling resolveTabId with a targetId captured earlier whose page has since been closed; the tab crashed and its target was replaced with a new targetId; prerendered/portal pages swapping target ids on activation; passing a targetId of a non-page target (iframe, background_page) that refreshMappings skips.
Common situations: Queue of saved targetIds processed after the user closed pages; automation resuming from disk state across a Chrome relaunch (target UUIDs are not stable across restarts); iframe targetIds mistakenly used where page targetIds are required; tab discard/hibernation extensions replacing targets.
Related errors
- No targetId for tab ${tabId} — page may have been closed
- Tab ${tabId} no longer exists
- Requires up-to-date Chrome extension (nativeClick).
- Cannot create ${role} tab group without tabs
- Failed to create tab lease in automation container
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ec522de637bd74c1.
Report an issue: GitHub.