Skip to content

Usage → Limits merges two Codex subscriptions that share an email address #10835

Description

@uzairansaruzi

What happened

I have two ChatGPT subscriptions, a Business one and a personal Plus one. I set them up as
two Codex provider instances using the shared CODEX_HOME plus shadow home layout from the
Codex provider docs. Both instances work and both report as authenticated, each showing its
own plan in provider settings.

Under Usage → Limits, only one of the two Codex accounts appears. I expected to see both,
one segment per account, the way the docs describe pooling.

Diagnosis

T3 Code collects usage limits from both instances correctly. The provider status caches for
both instances were refreshed in the same batch and each holds distinct, live data:

Instance Plan Windows reported Used Reset credits
A ChatGPT Business Subscription weekly only 8% 0
B ChatGPT Plus Subscription 5-hour and weekly 0% and 26% 3

The loss happens in the display layer, in packages/shared/src/usageLimits.ts.

accountKey (line 148) identifies a subscription account by provider driver plus lowercased
email address:

function accountKey(driver: ServerProvider["driver"], email: string | undefined): string | null {
  const normalizedEmail = email?.trim().toLowerCase();
  return normalizedEmail ? `${driver}:${normalizedEmail}` : null;
}

collectLimitAccounts (line 187) merges on that key at line 257. Both of my Codex logins
report the same email address, so both instances collapse into one LimitAccount and only
one set of windows survives.

The assumption is stated in the LimitAccount doc comment at line 153:

The same email signed in natively on two environments, or reported by a hub as well as
natively, is one account: its quota is one bucket, so counting it twice would misstate
what is left.

That holds for one subscription signed in twice. It does not hold here. A single ChatGPT
email can carry a personal Plus subscription and separately belong to a Business workspace.
Those are two subscriptions with two independent quotas, so this merge understates what is
available rather than avoiding a double count.

Which of the two survives is arbitrary. The merge keeps the fresher snapshot, compared with
a strict > at line 220. Both instances are probed in the same pass and carry an identical
checkedAt, so the comparison is false and whichever instance is iterated first wins.

The plan string that distinguishes them is already carried on the account as provider.auth.label,
so the two subscriptions are separable with data the collector already has.

Two other call sites use the same key and may want the same treatment. collectLimitSources
uses it at lines 105 and 133. collectProviderUsageLimits at line 639 pushes one entry per
native instance and uses the key only to suppress hub duplicates, so it looks unaffected, but
I did not verify its rendered output.

Steps to reproduce

  1. Have one ChatGPT email address that holds a personal Plus subscription and also belongs to
    a ChatGPT Business workspace. These are two subscriptions with separate quotas.
  2. Sign the first subscription into the default Codex home with codex login.
  3. Sign the second one into a shadow home:
    mkdir -p ~/.codex-t3/personal
    CODEX_HOME=~/.codex-t3/personal codex login
  4. In Settings → Providers, add a second Codex instance. Leave its CODEX_HOME path at the
    default and set its Shadow home path to ~/.codex-t3/personal. Give the two instances
    different display names.
  5. Confirm both instances show as authenticated in provider settings, each naming its own plan.
  6. Open Usage → Limits.

Expected: both Codex accounts appear, each contributing its own windows and its own segment.

Actual: one Codex account appears. The other subscription's quota is not shown anywhere on
the page.

Version

0.0.41-nightly.20260908.1414

Environment

macOS 26.6.2 (arm64), Node v24.14.0, codex-cli 0.153.4

Evidence

Provider status caches, two Codex instances, same refresh batch. Redacted.

instance A (display name "Business")
  auth.label                       ChatGPT Business Subscription
  usageLimits.checkedAt            2026-09-08T20:47:48.157Z
  usageLimits.windows[0].kind      weekly    id=primary    usedPercent=8
  usageLimits.windows[0].resetsAt  2026-09-15T18:08:38.000Z
  usageLimits.resetCredits.availableCount  0

instance B (display name "Personal", shadow home)
  auth.label                       ChatGPT Plus Subscription
  usageLimits.checkedAt            2026-09-08T20:47:48.157Z
  usageLimits.windows[0].kind      session   id=primary    usedPercent=0
  usageLimits.windows[0].resetsAt  2026-09-09T01:47:49.000Z
  usageLimits.windows[1].kind      weekly    id=secondary  usedPercent=26
  usageLimits.windows[1].resetsAt  2026-09-15T04:07:47.000Z
  usageLimits.resetCredits.availableCount   3

The auth email in both caches is byte-identical, confirmed by comparing SHA-256 digests
without reading the values. Identical checkedAt in both, so the line 220 freshness
comparison is false and merge order decides the winner.

Related issues

None found. I searched the tracker for usage limit account merging, duplicate accounts,
pooled limits, and same-email variations, and nothing matches. #9040, #9884, #7170 and
#9676 all touch DPoP or relay authentication and are unrelated.

Fix applied or workaround

None. Nothing was changed on the machine. There is no setting that disables the
deduplication, so there is no in-app workaround. Reading the second subscription requires
going outside T3 Code.

Filed by

Claude Code (claude-opus-5, 1M context) via t3 triage

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions