{"record":{"id":"ec522de637bd74c1","repo":"jackwener/OpenCLI","slug":"page-not-found-targetid-stale-page-identity","errorCode":null,"errorMessage":"Page not found: ${targetId} — stale page identity","messagePattern":"Page not found: (.+?) — stale page identity","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extension/src/identity.ts","lineNumber":44,"sourceCode":"\n  const result = tabToTarget.get(tabId);\n  if (!result) throw new Error(`No targetId for tab ${tabId} — page may have been closed`);\n  return result;\n}\n\n/**\n * Resolve tabId for a given targetId.\n * Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets().\n * Throws if no tabId can be found — never falls back to guessing.\n */\nexport async function resolveTabId(targetId: string): Promise<number> {\n  const cached = targetToTab.get(targetId);\n  if (cached !== undefined) return cached;\n\n  await refreshMappings();\n\n  const result = targetToTab.get(targetId);\n  if (result === undefined) throw new Error(`Page not found: ${targetId} — stale page identity`);\n  return result;\n}\n\n/**\n * Remove mappings for a closed tab.\n * Called from chrome.tabs.onRemoved listener.\n */\nexport function evictTab(tabId: number): void {\n  const targetId = tabToTarget.get(tabId);\n  if (targetId) targetToTab.delete(targetId);\n  tabToTarget.delete(tabId);\n}\n\n/**\n * Full refresh of targetId ↔ tabId mappings from chrome.debugger.getTargets().\n */\nasync function refreshMappings(): Promise<void> {\n  const targets = await chrome.debugger.getTargets();","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/extension/src/identity.ts#L26-L62","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst tabId = await resolveTabId(targetId);\n// after\nconst exists = (await chrome.debugger.getTargets()).some(t => t.id === targetId && t.type === 'page');\nconst tabId = exists ? await resolveTabId(targetId) : await reopenPageAndResolve(savedUrl);","handlingStrategy":"try-catch","validationCode":"const known = (await chrome.debugger.getTargets()).some(t => t.id === targetId && t.type === 'page');\nif (!known) throw new StalePageIdentityError(targetId);","typeGuard":null,"tryCatchPattern":"try {\n  tabId = await resolveTabId(targetId);\n} catch (err) {\n  if (err instanceof Error && /Page not found/.test(err.message)) {\n    return recoverPageByKey(pageKey); // re-acquire via URL/session key, never reuse the stale targetId\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["chrome-extension","target-lifecycle","identity-mapping","stale-reference"],"backgroundTag":"stale-target-reference","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}