docs/claude-multi-account-and-status-items.md
Status: Phase 1 design accepted; do not merge an implementation until its independent adapter and model proof is complete.
Related: #1756, #1268, and the bounded Claude sign-in repair in #1811.
claude-swap adapter as the first Claude subscription multi-account source.cswap --switch-to <slot> --json only after a separate product
decision and explicit user action.This solves the durable OAuth refresh problem without making CodexBar a second credential vault. It also avoids a Claude-only status item implementation that would need to be redesigned for Codex and other providers.
CodexBar has three account concepts today:
ProviderTokenAccount stores a label and one token plus optional provider metadata. It has no refresh token or
expiry model. Claude entries therefore work for session cookies, Admin API keys, or short-lived OAuth access tokens,
but they are not durable multi-subscription OAuth sessions.TokenAccountUsageSnapshot and CodexAccountUsageSnapshot separately project multi-account usage into menus.
Status items remain provider-scoped: StatusItemIdentity has only merged and provider, and
statusItems is keyed by UsageProvider.The recently merged #1800 scopes Claude OAuth history to the routed Keychain identity. #1776 prevents CLI-runtime usage refreshes from delegating credential repair to Claude Code, while app and user-initiated repair remain available. Both changes improve single-active-account correctness; neither discovers or displays multiple subscriptions.
The closed #1707 should not be revived. It coupled account discovery, credential resolution, provider routing, menu rendering, and animation across a large patch while broadening Keychain and prompt behavior. The safer seam is a credential-free usage adapter first.
| Option | Credential ownership | Durability | Risk | Recommendation |
|---|---|---|---|---|
| First-party OAuth account vault | CodexBar | High | New login, refresh, storage, revocation, migration, and security surface | Defer |
Read-only claude-swap adapter | claude-swap | High | External executable and schema dependency | Phase 1 |
| Discover Claude Code Keychain entries | Claude Code / ambiguous | Unknown | Undocumented enumeration; prompt and identity hazards | Reject |
| Existing token accounts | CodexBar config | Low for OAuth | Access token expires without refresh metadata | Keep for current cookie/API-key uses |
As of claude-swap v0.16.0,
cswap --list --json still returns a versioned object with schemaVersion: 1, an active account number, account slots,
redaction-sensitive email labels, 5-hour and 7-day usage percentages, and reset timestamps. Handled failures return an
error object and non-zero exit. v0.16.0 also adds mutation-capable automatic switching; that does not expand this
integration. CodexBar does not need --token-status, credential files, Keychain access, or raw OAuth values for the
display-only phase.
cswap --list --json. Never invoke a shell or accept config-defined passthrough
arguments.schemaVersion == 1; reject unknown versions and partial top-level shapes.claude-swap:<slot>), not email or credential-derived values.claude-swap storage, Claude Code storage, environment credentials, or Keychain entries. The
subprocess remains solely responsible for its own credential access. The adapter copies only allow-listed
usage/identity fields into its model and never logs or persists raw stdout.auto, run, --switch, --switch-to, --add-account, export, import, purge, or any other command in
Phase 1.claude-swap see no behavior change.The executable is an optional external dependency, not a bundled component. Preferences should show detected version, last refresh, adapter errors, and a link to the upstream project; CodexBar should not install or update it.
Introduce one projection used by menus and status items rather than teaching status item code about Claude OAuth:
struct ProviderAccountUsageSnapshot: Identifiable {
let id: ProviderAccountIdentity
let provider: UsageProvider
let displayLabel: String
let isActive: Bool
let snapshot: UsageSnapshot?
let error: String?
let sourceLabel: String?
}
struct ProviderAccountIdentity: Hashable {
let source: String
let opaqueID: String
}
Adapters own identity conversion. UI receives a user alias or privacy-safe ordinal when personal information is hidden. No provider may fill identity, plan, or usage fields using another provider's data.
Existing TokenAccountUsageSnapshot and CodexAccountUsageSnapshot can migrate behind this projection in small,
separately reviewed steps. Their credential and refresh logic stays source-specific.
Proposed setting under each provider's Accounts section:
One provider icon (default; current behavior)Selected account icons, with up to four account checkboxesSelecting account icons replaces that provider's aggregate item; it does not add duplicates. Account items use a
stable StatusItemIdentity.account(provider:source:opaqueID:), preserve existing provider autosave names, and open the
provider menu focused on that account. A short user alias or ordinal badge distinguishes otherwise identical provider
icons. Hide Personal Info replaces labels with Account 1, Account 2, and so on.
Merge Icons continues to mean exactly one status item. Account-icon controls are disabled while it is enabled, with a button to turn Merge Icons off. Existing users and status item positions remain unchanged until they opt in.
The alternative proposed in the #1268 discussion is a per-account toggle that adds selected account items, leaves unselected accounts under the provider item, and coexists with Merge Icons. That is more granular, but it creates duplicate provider/account items, makes “Merge Icons” no longer mean one item, and multiplies autosave and recovery states. The replacement mode above is the recommendation; if maintainers prefer the additive mode, grouping and Merge Icons semantics must be decided before implementation.
The mock above shows the recommended mode and its Merge Icons conflict. It is intentionally a decision artifact, not an implementation screenshot. The following packaged synthetic-account proof verifies the bounded current behavior: the account action is now named “Sign in with Claude Code…” and no longer claims it will add a durable CodexBar account. No real credential, browser session, or provider call was used.
claude-swap dependency is accepted only for exact read-only cswap --list --json execution.Any change to these decisions requires a new product/auth review before implementation because it changes storage, status item migration, process authority, or the credential boundary.
make check, make test, packaged synthetic proof, and macOS UI proof with redacted fixtures.No credential import, account mutation, or compatibility shim is part of this proposal.