{"record":{"id":"1e19746f2dd35ddf","repo":"jackwener/OpenCLI","slug":"cannot-create-role-tab-group-without-tabs","errorCode":null,"errorMessage":"Cannot create ${role} tab group without tabs","messagePattern":"Cannot create (.+?) tab group without tabs","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extension/src/background.ts","lineNumber":847,"sourceCode":"  ids: number[],\n): Promise<OwnedContainerGroup> {\n  if (ids.length === 0) return group;\n  await ensureTabsInWindow(ids, group.windowId);\n  const tabs = await Promise.all(ids.map((id) => chrome.tabs.get(id).catch(() => null)));\n  const missing = tabs\n    .filter((tab): tab is chrome.tabs.Tab => tab !== null && tab.id !== undefined && tab.groupId !== group.id)\n    .map((tab) => tab.id!);\n  if (missing.length > 0) await chrome.tabs.group({ groupId: group.id, tabIds: missing });\n  updateOwnedSessionWindowForTabs(role, ids, group.windowId);\n  return group;\n}\n\nasync function createOwnedGroup(\n  role: OwnedWindowRole,\n  windowId: number,\n  ids: number[],\n): Promise<OwnedContainerGroup> {\n  if (ids.length === 0) throw new Error(`Cannot create ${role} tab group without tabs`);\n  await ensureTabsInWindow(ids, windowId);\n  const groupId = await chrome.tabs.group({ tabIds: ids, createProperties: { windowId } });\n  ownedContainers[role].groupId = groupId;\n  ownedContainers[role].windowId = windowId;\n  // Record in the ledger and persist BEFORE the title/color update lands so a\n  // worker crash between the two API calls can self-heal on resume:\n  // `ensureCanonicalGroupTitle` repairs the title on the next ensure cycle\n  // once the ledger surfaces the untitled orphan. We must not `tabs.ungroup`\n  // on failure or the recorded id dangles.\n  if (role === 'interactive') interactiveGroupLedger.add(groupId);\n  await persistRuntimeState();\n  const group = await chrome.tabGroups.update(groupId, {\n    color: OWNED_TAB_GROUP_COLOR,\n    title: CONTAINER_TAB_GROUP_TITLE[role],\n    collapsed: false,\n  });\n  updateOwnedSessionWindowForTabs(role, ids, group.windowId);\n  return { id: group.id, windowId: group.windowId, title: group.title };","sourceCodeStart":829,"sourceCodeEnd":865,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/extension/src/background.ts#L829-L865","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait createOwnedGroup(role, windowId, ids);\n// after\nconst alive = await filterOpenTabIds(ids);\nif (alive.length === 0) return null; // skip grouping, nothing to own\nawait createOwnedGroup(role, windowId, alive);","handlingStrategy":"validation","validationCode":"const openIds = await Promise.all(ids.map(id => chrome.tabs.get(id).catch(() => null)));\nconst alive = ids.filter((_, i) => openIds[i]?.id != null);\nif (alive.length === 0) return; // nothing to group","typeGuard":"const hasTabs = (ids) => Array.isArray(ids) && ids.length > 0;","tryCatchPattern":"try {\n  await createOwnedGroup(role, windowId, ids);\n} catch (e) {\n  if (/without tabs/.test(e.message)) console.warn(`No tabs to group for ${role}; skipping`);\n  else throw e;\n}","preventionTips":["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"],"tags":["chrome-extension","tabs","internal","invariant"],"backgroundTag":"empty-tab-id-list","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}