jackwener/OpenCLI · error · Error
Cannot create ${role} 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.
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)
Solutions
- Ensure at least one open tab id exists before calling createOwnedGroup (filter dead ids and bail early if none)
- Recreate a lease via the normal open/automation entry point instead of regrouping an empty set
- 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
- Filter out closed/invalid tab ids before grouping
- After extension restart, rebuild the tab ledger before regroup operations
- Treat empty owned containers as a no-op rather than a grouping request
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
- Unexpected zsxq probe: ${JSON.stringify(probe)}
- Failed to create tab lease in automation container
- [opencli] Tab ${tabId} drifted to window ${tab.windowId} dur
- [opencli] Failed to recover drifted tab: ${moveErr}
- Chess.com callback returned an invalid response object for $
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/1e19746f2dd35ddf.
Report an issue: GitHub.