jackwener/OpenCLI · error · CommandFailure

bound_tab_not_debuggable

bound_tab_not_debuggable

Error message

Bound tab for session "${session.session}" is not debuggable (${preferredTab.url ?? 'unknown URL'}).

What it means

When a bound session has a preferredTabId, resolveTab fetches that preferred tab directly; if it exists but its URL is not debuggable (not http(s)/about:blank/data:), the library throws because CDP cannot attach to browser-internal pages. This is the preferred-tab branch of the same check as error 5210.

Source

Thrown at extension/src/background.ts:1537

        automationSessions.delete(leaseKey);
        throw new CommandFailure(
          'bound_tab_gone',
          `Bound tab for session "${existingSession.session}" no longer exists.`,
          'Run "opencli browser bind" again, then retry the command.',
        );
      }
      console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
    }
  }

  const existingPreferredTabId = existingSession?.preferredTabId ?? null;
  if (existingSession && existingPreferredTabId !== null) {
    const session = existingSession;
    try {
      const preferredTab = await chrome.tabs.get(existingPreferredTabId);
      if (isDebuggableUrl(preferredTab.url)) return { tabId: preferredTab.id!, tab: preferredTab };
      if (!session.owned) {
        throw new CommandFailure(
          'bound_tab_not_debuggable',
          `Bound tab for session "${session.session}" is not debuggable (${preferredTab.url ?? 'unknown URL'}).`,
          'Switch the tab to an http(s) page or run "opencli browser bind" on another tab.',
        );
      }
    } catch (err) {
      if (err instanceof CommandFailure) throw err;
      await removeLeaseSession(leaseKey);
      if (!session.owned) {
        throw new CommandFailure(
          'bound_tab_gone',
          `Bound tab for session "${session.session}" no longer exists.`,
          'Run "opencli browser bind" again, then retry the command.',
        );
      }
      return createOwnedTabLease(leaseKey, initialUrl);
    }
  }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Switch the bound tab to an http(s) page as the remediation hint says
  2. Re-run 'opencli browser bind' on another tab that shows web content
  3. Navigate programmatically: opencli browser navigate https://…
  4. Keep the bound tab pinned to real web pages during automation

Example fix

// before
preferredTab.url = 'chrome://newtab' → bound_tab_not_debuggable
// after
opencli browser navigate https://example.com   // then retry
Defensive patterns

Strategy: validation

Validate before calling

const preferred = await chrome.tabs.get(preferredTabId);
if (!/^https?:/i.test(preferred.url ?? '')) {
  await chrome.tabs.update(preferredTabId, { url: 'https://example.com' });
}

Type guard

function isWebUrl(url?: string): boolean {
  return !!url && (url.startsWith('http://') || url.startsWith('https://'));
}

Try / catch

try {
  await cmd();
} catch (e) {
  if ((e as Error).message.includes('not debuggable')) {
    await chrome.tabs.update(tabId, { url: START_URL });
    await retry(cmd);
  } else throw e;
}

Prevention

When it happens

Trigger: existingSession.preferredTabId !== null, chrome.tabs.get succeeds, but preferredTab.url fails isDebuggableUrl (chrome://, edge://, extension page, file://) and the session is unowned.

Common situations: The tab bound via 'opencli browser bind' was navigated to chrome://version, a Chrome Web Store page, or an internal new-tab page before running the next command.

Related errors


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