{"record":{"id":"ace3d51b731aa407","repo":"jackwener/OpenCLI","slug":"no-targetid-for-tab-tabid-page-may-have-been","errorCode":null,"errorMessage":"No targetId for tab ${tabId} — page may have been closed","messagePattern":"No targetId for tab (.+?) — page may have been closed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extension/src/identity.ts","lineNumber":28,"sourceCode":" *   - Miss triggers full refresh; refresh miss → hard error (no guessing)\n */\n\nconst targetToTab = new Map<string, number>();\nconst tabToTarget = new Map<number, string>();\n\n/**\n * Resolve targetId for a given tabId.\n * Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets().\n * Throws if no targetId can be found (page may have been destroyed).\n */\nexport async function resolveTargetId(tabId: number): Promise<string> {\n  const cached = tabToTarget.get(tabId);\n  if (cached) return cached;\n\n  await refreshMappings();\n\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}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/extension/src/identity.ts#L10-L46","documentation":"resolveTargetId (extension/src/identity.ts:28) maps an internal Chrome tabId to the CDP targetId that serves as the page's cross-layer identity. On cache miss it refreshes all mappings from chrome.debugger.getTargets(); if the tab still has no 'page'-type target, it throws rather than guessing. This is a hard signal that the tab no longer exists or is not a debuggable page.","triggerScenarios":"Calling resolveTargetId after the tab was closed (user or window.close), for a tab id that never existed, for non-page targets (devtools, extension pages, service workers have no tabId mapping), or a crash of the renderer that destroyed the target.","commonSituations":"Long-running automation holding a tabId across a user closing the tab; driving a tab opened in another window the user dismissed; Chrome restarted and tab ids changed; passing a stale tabId from persisted state after browser relaunch.","solutions":["Wrap the call in try/catch and treat the error as 'page gone': abandon or re-open the page and obtain a fresh tabId.","Before calling, verify the tab still exists via chrome.tabs.get(tabId).","If the tab was closed unintentionally, reopen it (chrome.tabs.create with the recorded URL) and re-resolve.","Don't cache tabIds across browser restarts; persist targetId/URL and re-resolve at session start.","Check that the target type is 'page' — service workers and devtools targets are intentionally excluded from the mapping."],"exampleFix":"// before\nconst targetId = await resolveTargetId(tabId);\n// after\nlet targetId: string;\ntry { targetId = await resolveTargetId(tabId); }\ncatch (e) {\n  const tab = await chrome.tabs.create({ url: savedUrl });\n  targetId = await resolveTargetId(tab.id!);\n}","handlingStrategy":"try-catch","validationCode":"let tabExists = true;\ntry { await chrome.tabs.get(tabId); } catch { tabExists = false; }\nif (!tabExists) throw new PageGoneError(tabId);","typeGuard":null,"tryCatchPattern":"try {\n  targetId = await resolveTargetId(tabId);\n} catch (err) {\n  if (err instanceof Error && /No targetId for tab/.test(err.message)) {\n    return handlePageClosed(tabId); // abandon, or chrome.tabs.create and re-resolve\n  }\n  throw err;\n}","preventionTips":["Listen to chrome.tabs.onRemoved and drop tabIds from your state as soon as tabs close.","Don't persist raw tabIds across browser restarts; store URLs and re-open instead.","Verify with chrome.tabs.get before long-running operations on a previously captured tabId.","Remember only 'page' targets with tabIds are mapped — service workers/devtools will always throw here."],"tags":["chrome-extension","tab-lifecycle","identity-mapping","stale-reference"],"backgroundTag":"stale-tab-reference","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}