{"record":{"id":"62ab1682a4162b89","repo":"jackwener/OpenCLI","slug":"tab-tabid-no-longer-exists","errorCode":null,"errorMessage":"Tab ${tabId} no longer exists","messagePattern":"Tab (.+?) no longer exists","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extension/src/cdp.ts","lineNumber":125,"sourceCode":"function isDebuggableUrl(url?: string): boolean {\n  if (!url) return true;  // empty/undefined = tab still loading, allow it\n  return url.startsWith('http://') || url.startsWith('https://') || url === 'about:blank' || url.startsWith('data:');\n}\n\nexport async function ensureAttached(tabId: number, aggressiveRetry: boolean = false): Promise<void> {\n  // Verify the tab URL is debuggable before attempting attach\n  try {\n    const tab = await chrome.tabs.get(tabId);\n    if (!isDebuggableUrl(tab.url)) {\n      // Invalidate cache if previously attached\n      attached.delete(tabId);\n      throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}`);\n    }\n  } catch (e) {\n    // Re-throw our own error, catch only chrome.tabs.get failures\n    if (e instanceof Error && e.message.startsWith('Cannot debug tab')) throw e;\n    attached.delete(tabId);\n    throw new Error(`Tab ${tabId} no longer exists`);\n  }\n\n  if (attached.has(tabId)) {\n    // Verify the debugger is still actually attached by sending a harmless command\n    try {\n      await sendDebuggerCommand({ tabId }, 'Runtime.evaluate', {\n        expression: '1', returnByValue: true,\n      }, CDP_PROBE_TIMEOUT_MS);\n      return; // Still attached and working\n    } catch {\n      // Stale cache entry — need to re-attach\n      attached.delete(tabId);\n    }\n  }\n\n  // Retry attach up to 3 times — other extensions (1Password, Playwright MCP Bridge)\n  // can temporarily interfere with chrome.debugger. A short delay usually resolves it.\n  // Normal commands: 2 retries, 500ms delay (fast fail for non-browser use)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/extension/src/cdp.ts#L107-L143","documentation":"In ensureAttached, if chrome.tabs.get(tabId) rejects for any reason other than this function's own 'Cannot debug tab' error, the library concludes the tab is gone, deletes the cached attach entry, and throws 'Tab ${tabId} no longer exists'. It guards subsequent CDP commands from operating on a dead target.","triggerScenarios":"chrome.tabs.get(tabId) fails inside ensureAttached — the tab was closed, its window closed, or an invalid tabId was passed — during any CDP operation (evaluate, screenshot, insertText, etc.).","commonSituations":"Script caches a tabId and reuses it after the user closed the tab; page navigations that destroy the tab (e.g. window.close()); passing a stale id from a previous session.","solutions":["Re-resolve a live tab before retrying (query chrome.tabs.query for a fresh tabId)","Retry after re-running the command that opens/targets the page","Validate the tabId exists right before each CDP call","Persist no tabIds across page/session boundaries; re-enumerate instead"],"exampleFix":"// before\nconst tabId = 42; // captured earlier\ncdp.evaluate(tabId, expr);\n// after\nconst [tab] = await chrome.tabs.query({ active: true, currentWindow: true });\nif (!tab?.id) throw new Error('no active tab');\ncdp.evaluate(tab.id, expr);","handlingStrategy":"retry","validationCode":"let exists = false;\ntry { await chrome.tabs.get(tabId); exists = true; } catch {}\nif (!exists) tabId = (await pickFreshTab()).id;","typeGuard":"async function resolveLiveTab(tabId: number): Promise<chrome.tabs.Tab> {\n  try { return await chrome.tabs.get(tabId); }\n  catch { const t = await chrome.tabs.query({ active: true, currentWindow: true });\n          if (!t[0]?.id) throw new Error('no live tab'); return t[0]; }\n}","tryCatchPattern":"try {\n  await cdp.screenshot(tabId);\n} catch (e) {\n  if ((e as Error).message === `Tab ${tabId} no longer exists`) {\n    tabId = await pickFreshTab();\n    return retry();\n  }\n  throw e;\n}","preventionTips":["Never cache tabIds across command batches — re-resolve each time","Listen for chrome.tabs.onRemoved and drop stale ids","Pass invalid ids deliberately only in tests","Keep automation tabs in a window the user won't close"],"tags":["cdp","tab-lifecycle","chrome-extension","stale-reference"],"backgroundTag":"tab-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}