jackwener/OpenCLI · error · Error

Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}

Error message

Cannot debug tab ${tabId}: URL is ${tab.url ?? 'unknown'}

What it means

cdp.ensureAttached verifies the target tab's URL is debuggable before attaching the Chrome debugger; URLs like chrome://, chrome-extension://, file://, or edge:// cannot be debugged via chrome.debugger, so the library throws and invalidates any cached attach state. This prevents confusing low-level debugger errors later.

Source

Thrown at extension/src/cdp.ts:119

  } finally {
    if (timer !== undefined) clearTimeout(timer);
  }
}

/** Check if a URL can be attached via CDP — only allow http(s) and blank pages. */
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);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Navigate the tab to an http(s) URL before running CDP commands
  2. Target a different tab that shows web content (opencli browser tab list to pick one)
  3. If file:// access is required, enable 'Allow access to file URLs' — note the library still treats file:// as non-debuggable, so move the page to a local http server
  4. Add a pre-check in your script that skips non-http(s) tabs

Example fix

// before
await cdp.evaluate(tabId, 'document.title');   // tab.url === 'chrome://version'
// after
const tab = await chrome.tabs.get(tabId);
if (/^https?:/.test(tab.url ?? '')) await cdp.evaluate(tabId, 'document.title');
Defensive patterns

Strategy: validation

Validate before calling

const tab = await chrome.tabs.get(tabId);
if (!tab.url || !/^https?:|^about:blank|^data:/i.test(tab.url)) {
  throw new Error(`skip CDP: non-debuggable url ${tab.url}`);
}

Type guard

function canAttach(url?: string): url is string {
  return !url || url.startsWith('http://') || url.startsWith('https://') || url === 'about:blank' || url.startsWith('data:');
}

Try / catch

try {
  await cdp.evaluate(tabId, expr);
} catch (e) {
  const m = (e as Error).message;
  if (m.startsWith('Cannot debug tab')) console.warn('navigate to http(s) first:', m);
  else throw e;
}

Prevention

When it happens

Trigger: Any CDP-backed operation (handleCdp, evaluate, screenshot, setFileInputFiles, ensureFrameTarget, insertText) targets a tabId whose current tab.url fails isDebuggableUrl.

Common situations: Automation pointed at the active tab after the user opened chrome://settings; screenshotting a Chrome Web Store page; running evaluate against a file:// page; new-tab page focused during a command.

Related errors


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