jackwener/OpenCLI · error · Error
Tab ${tabId} no longer exists
Error message
Tab ${tabId} no longer exists What it means
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.
Source
Thrown at extension/src/cdp.ts:125
function isDebuggableUrl(url?: string): boolean {
if (!url) return true; // empty/undefined = tab still loading, allow it
return url.startsWith('http://') || url.startsWith('https://') || url === 'about:blank' || url.startsWith('data:');
}
export async function ensureAttached(tabId: number, aggressiveRetry: boolean = false): Promise<void> {
// Verify the tab URL is debuggable before attempting attach
try {
const tab = await chrome.tabs.get(tabId);
if (!isDebuggableUrl(tab.url)) {
// Invalidate cache if previously attached
attached.delete(tabId);
throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}`);
}
} catch (e) {
// Re-throw our own error, catch only chrome.tabs.get failures
if (e instanceof Error && e.message.startsWith('Cannot debug tab')) throw e;
attached.delete(tabId);
throw new Error(`Tab ${tabId} no longer exists`);
}
if (attached.has(tabId)) {
// Verify the debugger is still actually attached by sending a harmless command
try {
await sendDebuggerCommand({ tabId }, 'Runtime.evaluate', {
expression: '1', returnByValue: true,
}, CDP_PROBE_TIMEOUT_MS);
return; // Still attached and working
} catch {
// Stale cache entry — need to re-attach
attached.delete(tabId);
}
}
// Retry attach up to 3 times — other extensions (1Password, Playwright MCP Bridge)
// can temporarily interfere with chrome.debugger. A short delay usually resolves it.
// Normal commands: 2 retries, 500ms delay (fast fail for non-browser use)View on GitHub (pinned to 49907e53dc)
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
Example fix
// before
const tabId = 42; // captured earlier
cdp.evaluate(tabId, expr);
// after
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) throw new Error('no active tab');
cdp.evaluate(tab.id, expr); Defensive patterns
Strategy: retry
Validate before calling
let exists = false;
try { await chrome.tabs.get(tabId); exists = true; } catch {}
if (!exists) tabId = (await pickFreshTab()).id; Type guard
async function resolveLiveTab(tabId: number): Promise<chrome.tabs.Tab> {
try { return await chrome.tabs.get(tabId); }
catch { const t = await chrome.tabs.query({ active: true, currentWindow: true });
if (!t[0]?.id) throw new Error('no live tab'); return t[0]; }
} Try / catch
try {
await cdp.screenshot(tabId);
} catch (e) {
if ((e as Error).message === `Tab ${tabId} no longer exists`) {
tabId = await pickFreshTab();
return retry();
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.).
Common situations: 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.
Related errors
- No targetId for tab ${tabId} — page may have been closed
- Bound tab for session "${session.session}" is not debuggable
- bound_tab_gone
- bound_tab_not_debuggable
- Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/62ab1682a4162b89.
Report an issue: GitHub.