jackwener/OpenCLI · error · Error

No targetId for tab ${tabId} — page may have been closed

Error message

No targetId for tab ${tabId} — page may have been closed

What it means

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.

Source

Thrown at extension/src/identity.ts:28

 *   - Miss triggers full refresh; refresh miss → hard error (no guessing)
 */

const targetToTab = new Map<string, number>();
const tabToTarget = new Map<number, string>();

/**
 * Resolve targetId for a given tabId.
 * Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets().
 * Throws if no targetId can be found (page may have been destroyed).
 */
export async function resolveTargetId(tabId: number): Promise<string> {
  const cached = tabToTarget.get(tabId);
  if (cached) return cached;

  await refreshMappings();

  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;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wrap the call in try/catch and treat the error as 'page gone': abandon or re-open the page and obtain a fresh tabId.
  2. Before calling, verify the tab still exists via chrome.tabs.get(tabId).
  3. If the tab was closed unintentionally, reopen it (chrome.tabs.create with the recorded URL) and re-resolve.
  4. Don't cache tabIds across browser restarts; persist targetId/URL and re-resolve at session start.
  5. Check that the target type is 'page' — service workers and devtools targets are intentionally excluded from the mapping.

Example fix

// before
const targetId = await resolveTargetId(tabId);
// after
let targetId: string;
try { targetId = await resolveTargetId(tabId); }
catch (e) {
  const tab = await chrome.tabs.create({ url: savedUrl });
  targetId = await resolveTargetId(tab.id!);
}
Defensive patterns

Strategy: try-catch

Validate before calling

let tabExists = true;
try { await chrome.tabs.get(tabId); } catch { tabExists = false; }
if (!tabExists) throw new PageGoneError(tabId);

Try / catch

try {
  targetId = await resolveTargetId(tabId);
} catch (err) {
  if (err instanceof Error && /No targetId for tab/.test(err.message)) {
    return handlePageClosed(tabId); // abandon, or chrome.tabs.create and re-resolve
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/ace3d51b731aa407. Report an issue: GitHub.