pingdotgg / pingdotgg/t3code

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

Open
#10,835 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/shared/src/usageLimits.ts by reading accountKey, collectLimitSources, and collectLimitAccounts, especially the merge and freshness logic around the referenced lines. Trace the Usage → Limits rendering path and any existing usage-limit tests; done means both subscriptions with the same email appear with their separate windows while genuine duplicate sources remain correctly deduplicated.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.