jackwener/OpenCLI · error · Error

Cannot create tab group without tabs

Error message

Cannot create ${role} tab group without tabs

What it means

createOwnedGroup groups a set of tabIds into a chrome-managed tab group owned by OpenCLI (for a given role such as automation/UI containers). Chrome's tabs.group API cannot be called with zero tabs, so the function guards ids.length === 0 and throws a plain Error naming the role. It prevents an opaque Chrome API failure ('No tab IDs given') from escaping.

Solutions

  1. Ensure at least one open tab id exists before calling createOwnedGroup (filter dead ids and bail early if none)
  2. Recreate a lease via the normal open/automation entry point instead of regrouping an empty set
  3. If this happens on extension restart, clear/rebuild the persisted ownedContainers ledger

Example fix

// before
await createOwnedGroup(role, windowId, ids);
// after
const alive = await filterOpenTabIds(ids);
if (alive.length === 0) return null; // skip grouping, nothing to own
await createOwnedGroup(role, windowId, alive);
Defensive patterns

Strategy: validation

Validate before calling

const openIds = await Promise.all(ids.map(id => chrome.tabs.get(id).catch(() => null)));
const alive = ids.filter((_, i) => openIds[i]?.id != null);
if (alive.length === 0) return; // nothing to group

Type guard

const hasTabs = (ids) => Array.isArray(ids) && ids.length > 0;

Try / catch

try {
  await createOwnedGroup(role, windowId, ids);
} catch (e) {
  if (/without tabs/.test(e.message)) console.warn(`No tabs to group for ${role}; skipping`);
  else throw e;
}

Prevention

When it happens

Trigger: An internal caller passes an empty ids array — e.g. attempting to restore/persist a container whose ledger of tab ids is empty, or passing only already-closed tab ids after filtering.

Common situations: Extension restarted (worker crash) and resumed with an empty container ledger; all leased tabs were closed by the user before regrouping; a bug in tab-id collection filtering everything out.

Related errors


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

Appendix: source

Thrown at extension/src/background.ts:847

  ids: number[],
): Promise<OwnedContainerGroup> {
  if (ids.length === 0) return group;
  await ensureTabsInWindow(ids, group.windowId);
  const tabs = await Promise.all(ids.map((id) => chrome.tabs.get(id).catch(() => null)));
  const missing = tabs
    .filter((tab): tab is chrome.tabs.Tab => tab !== null && tab.id !== undefined && tab.groupId !== group.id)
    .map((tab) => tab.id!);
  if (missing.length > 0) await chrome.tabs.group({ groupId: group.id, tabIds: missing });
  updateOwnedSessionWindowForTabs(role, ids, group.windowId);
  return group;
}

async function createOwnedGroup(
  role: OwnedWindowRole,
  windowId: number,
  ids: number[],
): Promise<OwnedContainerGroup> {
  if (ids.length === 0) throw new Error(`Cannot create ${role} tab group without tabs`);
  await ensureTabsInWindow(ids, windowId);
  const groupId = await chrome.tabs.group({ tabIds: ids, createProperties: { windowId } });
  ownedContainers[role].groupId = groupId;
  ownedContainers[role].windowId = windowId;
  // Record in the ledger and persist BEFORE the title/color update lands so a
  // worker crash between the two API calls can self-heal on resume:
  // `ensureCanonicalGroupTitle` repairs the title on the next ensure cycle
  // once the ledger surfaces the untitled orphan. We must not `tabs.ungroup`
  // on failure or the recorded id dangles.
  if (role === 'interactive') interactiveGroupLedger.add(groupId);
  await persistRuntimeState();
  const group = await chrome.tabGroups.update(groupId, {
    color: OWNED_TAB_GROUP_COLOR,
    title: CONTAINER_TAB_GROUP_TITLE[role],
    collapsed: false,
  });
  updateOwnedSessionWindowForTabs(role, ids, group.windowId);
  return { id: group.id, windowId: group.windowId, title: group.title };

View on GitHub (pinned to 49907e53dc)