---
title: Account Indicator (top-right toolbar popover)
---

# Account Indicator (top-right toolbar popover)

## What it is

### What it shows

The account-indicator pill in the **top-right toolbar** of the desktop window opens a popover that lists every signed-in account. A header above the list reads **Accounts (N)**, where **N counts login accounts only** — API keys are credentials rather than accounts, so they are excluded from the number even though they still appear as rows in the list below. (Consequence: with API keys present the header number is intentionally lower than the row count; with no API keys the two match.) Each row shows the account email/name (kept fully readable on its own line), the plan label (e.g., "Max 20x"), two short usage bars labelled "5h" and "7d" — now kept **on one line** — with a countdown to the next reset of the more-pressuring window, and, on **inactive** accounts, a **Switch** button. The **active account is marked by an accent highlight** (accent left-border + accent-coloured email), NOT a separate green "Active" badge — the badge was removed (2026-06-26) because it collided with the load-balancing session count. Under load balancing, that per-account session chip (**"N running · M waiting"**) rides the **usage line** rather than the identity line, so it never crowds the email down to one letter. A single **compact** footer row at the bottom combines the account actions **Log In** and **API Key** (shown on the **Claude** tab when you have accounts — folded down 2026-06-29 from the former separate add-account row) with the **View usage stats** link, and on the right the staleness line (_"when was this data fetched?"_ — see [Footer staleness indicator](#footer-staleness-indicator) below) plus the refresh button. It is **text-only (no icons)** so all of it fits on one line at the same height as the "Other AI usage" row above it. The footer is shared by **every** tab, so the Claude-only Log In / API Key actions are hidden on the non-Claude tabs (Codex has its own `Log In with ChatGPT`). The old separate "Manage Accounts" button was removed; in the no-account state the existing "Sign In" prompt is shown instead. The mobile UI shows the same list inside the account-chip dropdown in the top-right, plus the non-Claude provider accounts — see [Mobile: non-Claude accounts](#mobile-non-claude-accounts) below.

## Where to find it

Click the **account-indicator pill** in the **top-right toolbar** of the desktop window to open the popover; it is the accounts surface of the app, whose headline is the plan-exhaustion forecast. On mobile, the same list lives inside the **account-chip dropdown** in the top-right, below which the non-Claude provider accounts appear. A tab that mirrors a Settings page carries its own **Manage in Settings** hand-off, and the footer's **Log In** / **API Key** actions sit on the Claude tab whenever you hold accounts.

## How it behaves

### Provider tabs — data-driven (which tabs appear)

The popover opens on a **Claude** tab and shows a segmented tab strip across the top. As of 2026-08-26 that strip is **data-driven**, not a fixed list — previously it was a hardcoded four (Claude / Codex / Z.ai / Grok). It now shows **Claude** (always, the anchor) plus a tab for **every provider you hold an account with and have set up**, and a trailing **"+"** that offers the account providers you haven't.

- **Only providers you hold an ACCOUNT with.** This popover is the accounts surface — its headline is the plan-exhaustion forecast — so a provider reaches the strip only when you have a plan or a login with it: a flat-plan provider (**Cursor, GPT, xAI Grok**), a multi-account login pool (**Codex, Z.ai, Grok**), or a CLI you sign into with your own account (**Gemini, Antigravity, Hermes, Kimi Code**). A pay-per-token API-key vendor (DeepSeek, OpenRouter, Meta, DeepInfra, CrofAI …) has no plan, quota or reset to manage here, so it never earns a tab NOR appears under **"+"** — you set those up from their Settings card instead. _(Added 2026-09-08 — with ~20 spawn toggles on, the strip showed a tab for literally every provider, burying the handful of accounts the popover exists to show. The first cut of the filter keyed on subscriptions only and dropped the four sign-in CLIs; a free login is still an account, so they were restored the same day.)_
- **"Set up" = engaged.** A provider earns a tab when it is enabled (`allow<X>SessionSpawn`), has a stored API key, or — for the multi-account pool providers — has ≥1 configured account. A provider you have not touched is offered under **"+"** instead. The candidate set mirrors the new-session picker (honours `showAlternateProviders` + the same unreleased-feature gates); non-pickable carriers (terminal, opencode, the custom-provider carriers) never get a tab.
- **Two kinds of tab body.** The rich **multi-account pool** providers — **Codex, Z.ai (glm), Grok** — keep their own dedicated tabs (documented below), each a condensed mirror of their Settings account list. **Every other account provider** — Cursor, Gemini, Antigravity, Hermes, Kimi Code, GPT, xAI Grok — routes through ONE shared **generic** tab: a compact mirror of that provider's Settings card (its on/off toggle, an inline add/replace of its API key when it takes a straightforward one, "get a key" / "install" links, and a **Manage in Settings** hand-off). The key value never reaches the renderer — the tab reads only the redacted _presence_ (`'***'`), never the key itself.
- **The "+" view.** The "+" tab lists every account provider you have not set up yet; picking one opens that provider's tab in add-mode (an inline key form, the login/add-account affordance for a pool provider, or the Settings hand-off for an install-heavy engine). It is shown only when there is at least one provider left to add.
- **Overflow.** With many providers set up the strip is content-width and **scrolls horizontally** (the active tab stays in view) rather than squeezing.

The whole strip is built from the shared provider registry + setup catalog by `src/renderer/src/components/ui/account/account-popover-tabs.ts`, so a provider added to the registry flows in automatically — no per-provider edit to the popover. Generic tab: `src/renderer/src/components/ui/account/ProviderAccountPopoverTab.tsx`; the "+" list: `src/renderer/src/components/ui/account/AddProviderList.tsx`; the derivation is unit-tested in `tests/unit/lib/account-popover-tabs.test.ts`. _(Added 2026-08-26 — user asked why Devin, a first-class provider, wasn't in the strip; the fix made the strip show any set-up provider with a "+" to add the rest.)_

### API Key account row — metered charges only

An **API-key** account row shows three cost figures (**24h / 7d / 30d**) that are the **real metered charges billed to that key** — the `api_cost_log` spend recorded against the account. This is what your provider's usage console (e.g. the Anthropic dashboard) shows for the key, so the two match. It **excludes coding-session usage**: a Claude CLI session's reported cost is an API-equivalent _usage value_, not metered billing — for a plan-covered subscription/OAuth session it is not real money at all — so blending it in used to make the row read far above the console (the $909-vs-$0.91 report, fixed 2026-07-31 — feedback 11f2a824). Your coding-session usage still appears in the per-account usage bars and the **Stats → Spend** tab, which labels it as usage value and splits out what was billed out of pocket. Hover the figures for a one-line reminder of what they mean. Full invariant: `central-ai-spend-contract.md` `api-key-row-shows-metered-spend-only`.

### Needs-re-login count

Next to the **Accounts (N)** header a small amber **"N need re-login"** tally (a login glyph + count) appears whenever one or more **login** accounts are in the broken auth state that shows the red "Authentication failed" / "Log In" row — an access token expired with no refresh token, a dead (revoked) refresh token, an open spawn auth-breaker, or a usage "Authentication failed" error. It counts **only "broken now"** accounts — an account merely _expiring soon_ (but still working) is not counted — and is **hidden entirely when the count is zero**, so a healthy pool stays uncluttered. This badge is **desktop-only**: the mobile account chip briefly showed the same signal as an icon-only amber login glyph (2026-07-23) but that cue was **removed 2026-07-24 at the owner's request** ("I don't want to see the re-login symbol on mobile in the header") — on mobile the broken-account state is still visible inside the chip's dropdown (per-account error text + the "Sign in again" badge). The desktop badge and each red account row both derive from the ONE shared `loginNeedsReauth` predicate in `src/shared/account-usability.ts`, so the tally can never disagree with the rows it summarizes. Its colour is the semantic `text-status-needs-you` token (theme-safe). Full invariants: `account-reauth-count-contract.md`. _(Added 2026-07-23 — user-requested "a small number showing how many accounts need to be re-logged in"; mobile cue removed 2026-07-24.)_

### Billing-shutdown row — parked, not broken

A login account manually flagged **"Shut down (billing)"** (from its row's **Mark as billing shut down** menu item) wears a **neutral** "Shut down (billing)" badge — `surface` tokens, deliberately **not** the amber auth-failed tone, because a billing pause is _parked_, not broken (re-login can't fix it). Omniscio **never starts sessions on it** until you pick **Re-enable account** on the same row. The row **stays visible** (it isn't hidden) precisely so it can be re-enabled after you pay; the flag is **durable and sticky** — re-login does not clear it, only the explicit re-enable. The flag is set through the desktop UI / CLI (`POST /accounts/:id/billing-shutdown`); the mobile web embed **cannot** toggle it (channel blocked) but shows the badge as a read-only status. Full gating rules + surface contract: `auth-login-contract.md`. _(Added 2026-08-10 — billing-shutdown account marker.)_

### Codex usage section

On the popover's **`Codex` tab** (below the managed Codex accounts) the popover shows a **"Codex usage"** section when you've run Codex sessions. _(Through 2026-06-25 this sat on the default **Claude** view; it moved to the **Codex tab** on 2026-06-26 — it was redundant clutter on the Claude view next to a dedicated Codex tab, and now lives beside the Codex accounts it describes.)_ It has two parts, each shown only when real data exists:

- **Plan rate-limit bars (remaining usage).** When a Codex session reports your ChatGPT-plan limits, the section shows two usage bars — the **~5-hour** and **weekly** windows — with **% used and a countdown to reset**, using the _same `MiniUsageBar` the per-account Claude rows use_. A small **"as of Xm ago"** caption marks the numbers as a snapshot from the last Codex turn, not live. These appear only under a **`codex login` (ChatGPT) account** — on an OpenAI **API key** there is no plan window, so no bars.
- **Tokens-used cells (24h / 7d / 30d).** The tokens Codex has consumed, formatted like `1.2M tok`. **Tokens only — no dollar figure**: Codex bills against a ChatGPT **subscription**, so any `$` figure is only an Omniscio estimate (not metered spend) — showing one here would mislead. (Omniscio does estimate Codex per-turn cost and dual-writes it to `api_cost_log` under `source='codex'`; the popover's separate **Other AI usage** section EXCLUDES it for exactly that reason — see below. This section stays token-only.) OpenAI's own dashboard is authoritative for Codex spend.

The quota numbers come from the free `account/rateLimits/updated` push the Codex engine already emits as turns run (no extra request, no token cost), cached pool-wide and served over the `account:codex-ratelimits` IPC channel. The tokens come from `api_cost_log` rows (`source = 'codex'`) summed across all accounts via `account:codex-usage`. Both are pool-wide (Codex uses one shared OpenAI identity), and the section **self-hides when there are neither bars nor tokens** (and hides on a failed fetch, so you never see stale zeros). See `provider-registry-contract.md` for the full invariants. The per-session context ring for Codex sessions is documented in [Context Details](context-details.md).

> **Correction (2026-06-11):** an earlier version of this page said OpenAI "does not report a plan quota to Omniscio" so there could be no quota bar. That was true only of the **token-usage** notification (tokens only) — the Codex app-server's **account surface** (`account/rateLimits/*`) does report the plan windows, which is what the bars above now use. The `$`-figure caveat still stands (no pricing table).

### Codex accounts tab (managed-account mirror)

Separate from the pool-wide "Codex usage" section above, the popover now has a **`Codex` tab** (2026-06-13) that mirrors the **Omniscio-managed Codex accounts** list — the same rows you manage in **Settings → Accounts → Codex**, in condensed form. Each row shows the account email, plan label, the ~5h / weekly remaining-usage bars when available, and `Active` / `Switch` / `Remove` — backed by the **exact same store slice (`codexAccounts`) and IPC** as Settings (rendered through the shared `CodexAccountRow`), so the two surfaces never disagree. It also carries a `Log In with ChatGPT` action that allocates a new isolated-`CODEX_HOME` slot. The tab refetches on mount and on every `ACCOUNTS_CHANGED` push so multiple windows stay in sync.

It is deliberately **lighter** than Settings:

- **No balancing header** — v1 omits Claude's balancing layer for Codex (no "accounts in use" count, no "Balancing" label, no spillover, no pool coverage forecast). The active managed account is simply the default for new Codex sessions until you switch it.
- **No OpenAI API-key control** — Settings owns that; the popover stays minimal.

The pool-wide **"Codex usage" section above and this per-account list now sit together on the Codex tab** (as of 2026-06-26): the usage section is one global rollup of plan bars + token totals across all Codex sessions; this list is the **per-account rows** with switch/remove actions. Source: `src/renderer/src/components/ui/account/CodexAccountsPopoverTab.tsx`; behavior + invariants in [codex-provider.md § Managed Codex accounts](codex-provider.md#managed-codex-accounts) and `provider-registry-engines-core-codex-persistent-external-contract.md` — the managed-account rules, from `a-managed-account-is-an-isolated-home` through `balancing-is-entitled-and-off-by-default`.

### Grok accounts tab (managed-account mirror)

The popover also has a **`Grok` tab** that mirrors the **Omniscio-managed Grok accounts** list — the same rows you manage in **Settings → Accounts → Grok**, in condensed form. Each row shows the account label, a status dot, and `Active` / `Switch` / `Remove` — backed by the **exact same store slice (`grokAccounts`) and IPC** as Settings (rendered through the shared `GrokAccountRow`), so the two surfaces never disagree. It carries a `Log in with Grok` action that allocates a new isolated-`GROK_HOME` slot and opens `grok login`. The tab refetches on mount and on every `ACCOUNTS_CHANGED` push so multiple windows stay in sync.

It is deliberately **lighter** than the Codex tab:

- **No usage section** — Grok reports no email/plan/quota, so there are no usage bars (each row is just label + status dot).
- **No balancing header** — v1 is manual-routing; the active managed account is simply the default for new Grok sessions until you switch it.
- **Single-account cap in a shipped build** — the `Log in` affordance is hidden once one Grok account exists (the first sign-in is always allowed); owner/dev builds keep unlimited accounts. It is also disabled off-desktop, since `grok login` needs a terminal.

Source: `src/renderer/src/components/ui/account/GrokAccountsPopoverTab.tsx`; provider behavior + the Settings sibling in [grok-provider.md](grok-provider.md). _(Added 2026-08-24 — the popover mirror of the managed-Grok Settings tab.)_

### Mobile: non-Claude accounts

The phone's account-chip dropdown shows the **Claude** accounts it always did, and — below them — the **non-Claude provider accounts** (**Codex**, **Z.ai**, **Grok**) with their usage, in one labelled group per provider. A group appears only when you actually hold an account with that provider, so a Claude-only user sees no change at all.

- **Same rows as desktop.** Each group renders the SAME components the desktop popover's provider tabs use (`CodexAccountRow` / `GlmAccountRow` / `GrokAccountRow`, all over the shared `ProviderAccountRow`), so Codex plan windows, Z.ai quota windows and the Grok label can never drift between the two surfaces.
- **Read + Switch only — no Add, no Remove.** A paired phone is deliberately refused `account:codex-add-login` / `-remove`, the Grok and Z.ai equivalents, and `account:remove`, so the mobile rows pass no remove handler and the shared row then draws no Remove button; no add affordance is offered either. **Rename stays** on a Z.ai row, because `account:glm-rename` IS reachable from a phone. Account management remains a desktop job.
- **The phone loads this itself.** Nothing else does: the 5-minute account poll is started only when the app runs under Electron, so in the mobile web embed the section's mount effect is the sole source of these accounts. It fires four cheap reads — the three account lists plus the Z.ai usage refresh — without awaiting them, so the menu never waits on them and no provider process is spawned.
- **A failed read looks like "no accounts."** Accepted deliberately: a failure there means the same local connection the whole menu already depends on is down, and the section is purely additive — it never shows anything false.

Source: `src/renderer/src/components/ui/account/MobileProviderAccounts.tsx`, mounted by `src/renderer/src/components/ui/MobileAccountChip.tsx`. _(Added 2026-09-18 — owner request: "I need to be able to see non Claude account usage on mobile just like Claude.")_

### Other AI usage section

Below the accounts list (on the **Claude** tab) a single compact row reads **"Other AI usage"** with three amounts — **24h / 7d / 30d** — summing what Omniscio spends on its own **background** AI features (session titles, Plain Speak, AI Suggestions, email summaries, the Inbox Pilot, digests, voice, calendar AI, …) across every account. It is the global rollup of the `api_cost_log` table's feature rows — **separate from your coding**, which is already shown by the per-account bars above and the Spend tab.

- **Hovering the row opens a per-feature breakdown table** — **Feature | 24h | 7d | 30d**, sorted by 30d, with a muted `—` in any window a feature didn't spend in (and `<$0.01` for sub-cent spend). (Through 2026-07-08 the hover only showed the 24h breakdown; it now covers all three windows.)
- **It does NOT include Codex or any coding-engine spend.** Those engines dual-write the same dollars that already appear in the per-account bars / Codex tab, and Codex bills against a ChatGPT subscription so its "cost" is an estimate — counting it here used to inflate the section ~73–90% (fixed 2026-07-08 by applying the Spend tab's engine-source exclusion). Your Codex usage lives in its own token-only **Codex usage** section on the Codex tab.
- **If the fetch fails** the whole section hides (never shows stale zeros); a successful fetch with no spend shows `$0.00` and "No activity in the last 30d" on hover.

### How the list is ordered

The popover shows accounts in **switch-priority order** — the same order the spawn pool would rotate through. Tiers (top → bottom):

1. **Active account** — pinned to the top regardless of capacity. **Exception:** an active API key account does not pin; it falls through to the absolute bottom (tier 5) like any other API key. Active login accounts still pin normally.
2. **Login accounts with primary capacity** — sorted by `max(5h util, 7d util)` ascending. Lowest score (most headroom) wins. An account with no usage data at all (cold start, missing fetch) is treated as score 0 and ranks at the top of this tier.
3. **Login accounts with only extra-usage capacity** — primary maxed out but extra-usage credits remain. Sorted by remaining extra-usage credits descending.
4. **Exhausted login accounts** — no primary capacity, no extra-usage, OR known-dead OAuth token. Sorted by **"when can tokens next be available?"** ascending. The wait time is the LATER of the 5h and 7d resets (whichever is maxed) — because both windows must drop below 100% before the account can spawn again. An account whose 5h is exhausted but 7d is at 80% only waits for 5h's reset (7d is already available); an account whose 5h and 7d are both exhausted waits for whichever resets last. Accounts with no reset-time data fall to the bottom of this tier, then id-tiebreak.
5. **API key accounts** — sorted by id ascending. **Absolute bottom of the list.** Never appears above any login account regardless of capacity. (This is intentional — login accounts are the user's primary credentials; API keys are the fallback.)

Within tiers 2 and 3, ties on the primary score / extra-usage headroom are broken by account id ascending.

This is the same ranking the main-process `pickBestAccount()` walks when choosing the next account for a CLI session spawn. Both the indicator UI and the spawner call into `rankAccounts()` from `src/shared/account-pool.ts`, so the dropdown shows exactly what the spawner would pick.

The Settings → Accounts page now uses this **same ordering** — its Login Accounts and API Keys lists are sorted the same way as this dropdown (active account first, then by availability; API keys last) instead of the old unsorted order. Those rows also no longer print the email twice: when an account has no real name, the second line that would just repeat the email is hidden; an account with a genuine name (e.g. "Sam") still shows both lines.

### Dynamic re-sort on usage updates

The order recomputes on every render. When a usage push update arrives while the popover is open, rows reorder live so the dropdown always reflects the current state — the topmost non-active row is the next account the spawner would rotate to.

User-requested 2026-05-10: an earlier version of this feature froze the order on open to prevent rows shifting under a click. That was reverted because freezing made the dropdown lie about which account was currently the best switch target.

### Footer staleness indicator

A short text line at the bottom of the popover tells you how fresh the usage numbers are. The format depends on which usage payload Omniscio has on hand:

- **Active login user with usage data** — `Data from 11:19 AM · 5m ago`. The wall-clock time is when Omniscio last fetched the active account's usage; the relative suffix ("just now" / "Xm ago" / "Xh ago" / "Xd ago") tells you the same thing without forcing you to read the clock and do the math.
- **No active login usage but pool-wide data is available** — `Updated 11:19 AM · 5m ago`, same format.
- **Nothing fetched yet** — `Not checked`.
- **Stale fetch** (the last refresh attempt errored) — the suffix ` (stale)` is appended after the relative time, e.g. `Data from 11:19 AM · 12m ago (stale)`. The data shown is the last successful read; Omniscio tells you it tried to refresh and failed instead of silently presenting old numbers as current.

The relative suffix uses the shared `formatRelativeTime` helper from [/src/renderer/src/lib/date-utils.ts](/src/renderer/src/lib/date-utils.ts), so the wording matches every other "X ago" surface in the app (sidebar timestamps, inbox row times, run history dates).

**Live tick:** while the popover is **open**, a 30-second interval re-renders the footer so "5m ago" rolls forward to "6m ago" without waiting for the next usage refresh or a parent store update. The interval is gated on `showPopover` — it doesn't run while the popover is closed (no wasted CPU between opens), and it tears down cleanly when you close the popover.

User-requested 2026-05-11: the wall-clock time alone (`Data from 11:19 AM`) was easy to misread when the popover sat open for a while — the user couldn't tell at a glance whether 11:19 was 30 seconds ago or 90 minutes ago. The relative suffix removes that ambiguity.

### Why this order

A non-programmer user with 13 accounts asked for a glanceable "next account to switch to" gradient. Sorting by primary-bucket score ascending (lowest util = most headroom = best to switch to next) matches their phrased intent: "show me what the next account is going to be rotated to." Sharing the algorithm with the spawn pool means the indicator can never disagree with what actually happens on a session spawn.

Two refinements were added on 2026-05-10 after the user exercised the feature against their real account pool:

- **API at the absolute bottom.** Direct user quote: "I want the API to always be at the very bottom. I don't want to see the API in the list. It should always default to the very bottom." API keys are the fallback layer the spawner falls through to when no login account has capacity — the user wants them visually de-emphasized so the popover surfaces login accounts first, even exhausted ones. The active-API exception to top-pinning is part of the same intent: if API is currently active it stays at the bottom and a login account heads the list, which is what the user wants to see when deciding where to switch next.
- **Exhausted accounts sorted by "when can tokens next be available?"** Direct user scenario: you@example.com had 5h exhausted but 7d at 80% — tokens free up in 73 minutes (just waiting for 5h to reset). Another account had both windows maxed and 7d resets in 2 days 5 hours. The user expected the fresher account to rank above. The exhausted-tier sort now answers "which exhausted account frees up soonest?" using the LATER of the maxed-window resets (because both 5h and 7d have to be under 100% before the account can spawn — if 7d is already under 100%, only 5h's reset matters; if both are maxed, you wait for the later of the two).

## For agents

### Where the change is wired

- Shared helper: `src/shared/account-pool.ts` — `rankAccounts()` (full ranking) and `pickBestAccount()` (single-pick wrapper used by the spawner). `AccountWithUsage` carries an optional `nextAvailableMs` field — pre-computed delta-from-now in milliseconds, used by the exhausted-tier sort.
- Renderer wrapper: `src/renderer/src/lib/account-sort.ts` — `sortAccountsForIndicator(accounts, allUsage, activeAccountId, nowMs?)` maps `AccountSummary` + `AllAccountsUsage` → `AccountWithUsage[]` (including computing `nextAvailableMs` from `CliUsageBucket.resetsAt`) and delegates to `rankAccounts()`. `nowMs` defaults to `Date.now()`; exposed for deterministic tests.
- Desktop call site: `src/renderer/src/components/ui/AccountIndicator.tsx` (around the `accounts.map()` block)
- Mobile call site: `src/renderer/src/components/ui/MobileAccountChip.tsx` (around the `accounts.map()` block)
- Tests: `tests/unit/lib/account-sort.test.ts` (18 cases) and `tests/unit/services/account-pool.test.ts` (74 cases covering both `pickBestAccount` and `rankAccounts`)

## Related

The account rows themselves are documented on [Add a Claude account](add-a-claude-account.md), and what the spawner does with them is on [Account Pool (multiple Claude accounts + exhaustion alert)](account-pool.md). If an account has gone stale, [Sign accounts back in automatically (automated re-login)](account-relogin.md) covers driving the sign-in for you, and [usage-forecast.md](usage-forecast.md) explains the forecast row each login account shows in Settings.
