Skip to content

fix(shared): pooled limits weight each account by its plan size - #35427

Merged
juliusmarminge merged 2 commits into
mainfrom
factory/issue-13110-a13675
Nov 12, 2026
Merged

juliusmarminge merged 2 commits into
mainfrom
factory/issue-13110-a13675

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Cause: poolWindows in packages/shared/src/usageLimits.ts averaged members' usedPercent, the pace inputs (used and elapsed share) and each reset's restoresPercent evenly. It ignored LimitAccount.plan, so a ChatGPT Pro 20x and a Plus counted the same. Fix: a new planCapacity(account, kind) looks up two small tables keyed on the plan labels the server already emits. Codex session and weekly windows: 'ChatGPT Plus Subscription' 1, 'ChatGPT Pro 100 Subscription' 5, 'ChatGPT Pro 200 Subscription' 20 (labels from codexPlanLabel). Claude session windows only: Pro 1, Max 5, Ultra 20 (from claudeSubscriptionLabel, where max5 maps to Max and max20 to Ultra; the session-only rule follows the closed #13675). If any member of a pooled window has an unknown plan, every member counts once, as before. Examples of unknown plans: the hub's generic 'Claude Subscription', Pro 500, Business, Enterprise, other drivers. Otherwise used percent, pace and restoresPercent are weighted by capacity over the total weight. With a used-up Pro 200 and an unused Plus, the pool shows 5% left and the Pro reset restores 95%. Only the shared helper changes, so the web, desktop and mobile Limits views and the mobile widget all get the corrected figures. No new regex or parser: the lookups are plain object reads. The repro test from factory/repro-13110 is kept and now passes.

Fixes #13110

Reproduction on main

Commit f03db4f (origin/main). Copied apps/web/test/fixtures/usage-limits-hub/hub.mjs to scratch and changed plan_type to come from each scenario account. Scenario: Codex account 'pro20x' (plan_type pro, which the server labels 'ChatGPT Pro 200 Subscription', i.e. 20x) at 100% session and weekly used, and Codex account 'plus' (plan_type plus) at 0% used. Ran T3CODE_DEV_SEED=0 vp run dev with empty CODEX_HOME and CLAUDE_CONFIG_DIR, paired the browser, and added the hub under Settings > Providers > Usage providers > Add hub (http://127.0.0.1:18517, label 'Team hub'). Opened Usage > Limits. The Codex Weekly row showed '50% left' and '↻ +50%', and the Session row also showed 50% / +50%. Clicking the Pro segment showed Plan 'ChatGPT Pro 200 Subscription', Left 0%, 'Restores +50% of pool'. Also added a unit test to packages/shared/src/usageLimits.test.ts ('weights pooled Codex quota and resets by each account's plan capacity'). It fails on main with remainingPercent 50 (expected 5), and the Pro reset restores 50 (expected 95).

Evidence

Reproduced in Chromium, 1280×800, light theme, against fresh databases on both commits. Add synthetic Team hub under Settings > Providers, pooling an exhausted ChatGPT Pro 20x account with an unused Plus account. Open Usage > Limits, then click the exhausted Pro segment.

The scenario failed on base and passed on head; scenarios:prove exited 0. Base displayed 50% remaining and a 50% Pro reset contribution. Head displayed 5% remaining and a 95% Pro reset contribution.

Capture Before After
Commit f03db4fbef c8217e85ed
Session and Weekly Before: 50% left and +50% After: 5% left and +95%
Pro reset contribution Before: Pro restores 50% of pool After: Pro restores 95% of pool
Interaction recording Before After

Scenario and synthetic hub driver

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Mobile, the mobile widget, and the desktop shell were not run; this proof covers the web reproduction.
  • Other plan combinations, Claude plan weights, and unknown-plan fallback were not exercised; the synthetic hub covers the reported Codex Pro 20x/Plus combination.
  • Pace icons were captured, but numeric pace weighting was not independently verified.

Tests

vp test run packages/shared/src/usageLimits.test.ts -t 'plan capacity' with the fix stashed: 1 failed (expected 50 to be 5), so the bug reproduces. vp test run packages/shared/src/usageLimits.test.ts with the fix: 70/70 passed. vp run check:changed: all passed (format, lint, knip 1/2 and 2/2, typecheck of 35 packages, tests in 2 files with 72/72 passing). The pre-commit hook's fmt, lint and knip:check also passed.

Not in this change

  • The web and mobile pooled bars still draw each account's segment at equal width (gridTemplateColumns repeat(n) / barWidth / columns.length). Sizing segments by plan would change the look, so I left it for a separate decision.
  • No capacity entry for ChatGPT Pro 500, Business, Business Premium, Enterprise, Edu, Go or Free, because the brief gives no ratio for them. Pools that include them stay equally weighted.
  • The CLIProxyAPI hub labels Claude accounts with the generic 'Claude Subscription', which carries no tier. Hub-only Claude pools therefore stay equally weighted.

AI-assisted; verified with the tests and tools listed above.

…13110)

Fails on main: a used-up Pro 20x pooled with an unused Plus shows 50% left
and a 50% Pro reset instead of about 5% left and a 95% reset.

AI-assisted; verified with the tests and tools listed above.
Pooled Limits averaged members evenly, so a used-up ChatGPT Pro 20x
pooled with an unused Plus showed 50% left and a +50% Pro reset.

Weight pooled used percent, pace and reset shares by plan capacity:
Codex session and weekly Plus 1, Pro 100 5, Pro 200 20; Claude session
Pro 1, Max 5, Ultra 20. A pool with any unknown plan keeps equal weights.

Fixes #13110

AI-assisted; verified with the tests and tools listed above.
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Nov 12, 2026
"ChatGPT Pro 200 Subscription": 20,
};
// Claude's published plan multipliers describe the session allowance only.
const CLAUDE_SESSION_CAPACITY: Record<string, number> = { Pro: 1, Max: 5, Ultra: 20 };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/usageLimits.ts:548

Claude subscriptions are never assigned their published session weights, so a pool with an exhausted Ultra account and an unused Pro account reports 50% remaining instead of 5%. account.plan contains labels such as Claude Ultra Subscription, but CLAUDE_SESSION_CAPACITY is keyed by bare names; use the emitted full labels as the keys.

Suggested change
const CLAUDE_SESSION_CAPACITY: Record<string, number> = { Pro: 1, Max: 5, Ultra: 20 };
const CLAUDE_SESSION_CAPACITY: Record<string, number> = {
"Claude Pro Subscription": 1,
"Claude Max Subscription": 5,
"Claude Ultra Subscription": 20,
};
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/shared/src/usageLimits.ts around line 548:

Claude subscriptions are never assigned their published session weights, so a pool with an exhausted Ultra account and an unused Pro account reports 50% remaining instead of 5%. `account.plan` contains labels such as `Claude Ultra Subscription`, but `CLAUDE_SESSION_CAPACITY` is keyed by bare names; use the emitted full labels as the keys.

@macroscopeapp

macroscopeapp Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — Pooled usage displays now weight accounts by recognized plan capacity, including pace and reset contributions. The reported Claude-label mismatch remains subject to the separate correctness gate; the change otherwise affects informational displays rather than billing or enforced limits.

Needs human approval because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.5 KiB 21.5 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 1 1 0 (0.0%) 8 ✅

Other scenarios

All 64 scenario measurements match main.

All 64 scenario measurements
Provider Scenario Metric Main baseline This PR Impact PR ceiling
Codex short turn resume WebSocket decoded 10.5 KiB 10.5 KiB 0 B (0.0%) 12.7 KiB ✅
Codex short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn running, cold open HTTP decoded 281.7 KiB 281.7 KiB 0 B (0.0%) 302.7 KiB ✅
Codex long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Codex long turn running, history page HTTP decoded 92.4 KiB 92.4 KiB 0 B (0.0%) 107.4 KiB ✅
Codex long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Codex long turn finished, cold open HTTP decoded 381.5 KiB 381.5 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, socket cold open WebSocket decoded 381.6 KiB 381.6 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, work page HTTP decoded 186.2 KiB 186.2 KiB 0 B (0.0%) 200.2 KiB ✅
Codex long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Codex idle thread resume WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell snapshot HTTP decoded 41.6 KiB 41.6 KiB 0 B (0.0%) 53.7 KiB ✅
Codex fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Codex fleet shell subscribe WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Codex fleet shell resume check rows 0 0 0 0 ✅
Codex stale shell reload HTTP decoded 41.6 KiB 41.6 KiB 0 B (0.0%) 53.7 KiB ✅
Codex stale shell reload rows 27 27 0 (0.0%) 27 ✅
Codex stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Codex stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex stale todo list cold open HTTP decoded 72.4 KiB 72.4 KiB 0 B (0.0%) 83.0 KiB ✅
Codex stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Codex stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅
Claude short turn resume WebSocket decoded 10.6 KiB 10.6 KiB 0 B (0.0%) 12.7 KiB ✅
Claude short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn running, cold open HTTP decoded 283.3 KiB 283.3 KiB 0 B (0.0%) 302.7 KiB ✅
Claude long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Claude long turn running, history page HTTP decoded 93.4 KiB 93.4 KiB 0 B (0.0%) 107.4 KiB ✅
Claude long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Claude long turn finished, cold open HTTP decoded 384.2 KiB 384.2 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, socket cold open WebSocket decoded 384.3 KiB 384.3 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, work page HTTP decoded 188.1 KiB 188.1 KiB 0 B (0.0%) 200.2 KiB ✅
Claude long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Claude idle thread resume WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell snapshot HTTP decoded 42.2 KiB 42.2 KiB 0 B (0.0%) 53.7 KiB ✅
Claude fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Claude fleet shell subscribe WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Claude fleet shell resume check rows 0 0 0 0 ✅
Claude stale shell reload HTTP decoded 42.2 KiB 42.2 KiB 0 B (0.0%) 53.7 KiB ✅
Claude stale shell reload rows 27 27 0 (0.0%) 27 ✅
Claude stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Claude stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude stale todo list cold open HTTP decoded 73.4 KiB 73.4 KiB 0 B (0.0%) 83.0 KiB ✅
Claude stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Claude stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅

Baseline: a2685e6 · PR result: c8217e8 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Pooled usage, pace, and reset percentages now account for supported plan capacities. If any account lacks a known capacity for a window, the pool uses equal weights. A test checks weighted Codex weekly quota and reset contribution.

Changes: Usage pool weighting

Layer / File(s) Summary
Plan capacity lookup
packages/shared/src/usageLimits.ts
Adds capacity weights for Codex session and weekly windows, and Claude Agent session windows.
Weighted pool metrics
packages/shared/src/usageLimits.ts, packages/shared/src/usageLimits.test.ts
Weights pooled usage, timed usage, elapsed pace, and reset contributions when all capacities are known. Otherwise, all members have equal weights. The test checks a Codex Pro and Plus weekly pool.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • Claude Max 20x renamed Ultra and missing session windows: this change assigns Claude Agent Ultra a session capacity, but does not address missing session windows.

Possibly related PRs

Suggested reviewers: alexchambers, macroscopeapp, maria-rcks

Merge Risk: Minimal · up to c8217

Pooled limits now weight each account by its plan size, and unknown plans fall back to equal weighting. No merge-blocking risk was identified.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title "fix(shared): pooled limits weight each account by its plan size" follows the conventional commit format and names the primary change. The raw summary and objectives confirm the change weigh…
Linked Issues check Passed Active directly linked issue: #13110 (open). Closed #13675 is background only. Requirements from #13110: weight pooled Codex usage by plan capacity, weight pooled pace, and weight reset restoration; a…
Out of Scope Changes check Passed All changes are in packages/shared/src/usageLimits.ts (planCapacity tables, poolWindows weighting) and usageLimits.test.ts (one new test). Each change serves the pooled-limit weighting objective of #1…
Description check Passed The description states the problem (pooled limits averaged usedPercent, pace, and restoresPercent evenly, ignoring plan), the change (planCapacity lookup tables for Codex and Claude plans, equal-weigh…
Full details: Title check

Explanation

The title "fix(shared): pooled limits weight each account by its plan size" follows the conventional commit format and names the primary change. The raw summary and objectives confirm the change weights pooled usage, pace, and reset refill by plan capacity in the shared helper. The title is specific and does not describe anything absent from the changeset.

Full details: Linked Issues check

Explanation

Active directly linked issue: #13110 (open). Closed #13675 is background only. Requirements from #13110: weight pooled Codex usage by plan capacity, weight pooled pace, and weight reset restoration; a used-up Pro 20x plus unused Plus should show about 5% remaining and a Pro reset restoring about 95%.

Evidence in packages/shared/src/usageLimits.ts at the reviewed head: the new planCapacity(account, kind) reads CODEX_PLAN_CAPACITY (Plus 1, Pro 100 5, Pro 200 20) for codex session and weekly windows. poolWindows now computes usedPercent, timedUsed, meanElapsed, and restoresPercent using capacity weights over totalWeight. If any member has an unknown capacity, all members get weight 1. The shared helper feeds collectLimitPools, which the web, mobile, and widget views consume, so the pool figures reach those surfaces.

Evidence in packages/shared/src/usageLimits.test.ts: a new test 'weights pooled Codex quota and resets by each account's plan capacity' uses the #13110 scenario and asserts remainingPercent 5 and a Pro reset restoresPercent of 95. The PR reports this test fails on main and passes with the fix.

No unmet coding requirement from #13110 was found. Pooled bar segment widths are not required by #13110; the PR leaves them for separate work.

Full details: Out of Scope Changes check

Explanation

All changes are in packages/shared/src/usageLimits.ts (planCapacity tables, poolWindows weighting) and usageLimits.test.ts (one new test). Each change serves the pooled-limit weighting objective of #13110.

The Claude session capacity table (Pro 1, Max 5, Ultra 20) extends the same plan-size weighting to Claude session windows. It is a related extension, not an unrelated change. Claude weekly windows and unknown plans keep equal weights, as the PR describes. The closed #13675 is context only and is not used as a requirement.

No unrelated files or behavior changes were found in the diff.

Full details: Description check

Explanation

The description states the problem (pooled limits averaged usedPercent, pace, and restoresPercent evenly, ignoring plan), the change (planCapacity lookup tables for Codex and Claude plans, equal-weight fallback for unknown plans), and a linked issue (#13110). It includes reproduction steps, a unit test, a browser-level before/after proof with screenshots and a recording, and a clear 'Not shown' list. It follows the template sections Problem, Change, Scope, and Verification, and it discloses AI assistance. Minor gaps: the title is not restated in the description, the Scope section does not explicitly say why this is a bug fix rather than a feature, and the model and harness are named only as 'AI-assisted' without identifying the model and harness. These gaps are non-critical, so the description is mostly complete.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between a2685e6 and c8217e8.

Files selected for processing: 2

  • packages/shared/src/usageLimits.test.ts
  • packages/shared/src/usageLimits.ts

Limit details: You’ve used all 10 included reviews currently available.

@juliusmarminge
juliusmarminge merged commit 0b21a82 into main Nov 12, 2026
35 checks passed
@juliusmarminge
juliusmarminge deleted the factory/issue-13110-a13675 branch November 12, 2026 17:49
Pipyakas pushed a commit to Pipyakas/t3code that referenced this pull request Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pooled Limits average accounts evenly, ignoring plan size (Pro 20x counts the same as Plus)

1 participant