feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: display provider usage limits in settings - #1732

Open
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits
Open

feat: display provider usage limits in settings#1732
Aditya190803 wants to merge 21 commits into
pingdotgg:mainfrom
Aditya190803:feat/provider-usage-limits

Conversation

@Aditya190803

@Aditya190803Aditya190803 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#228.

What Changed

Added provider usage limits to the settings flow end to end for all 4 providers (Codex, Claude, Cursor, OpenCode):

  • the shared contract now includes the usage-limits schema
  • the server persists and exposes provider usage limits
  • the web app renders the limits in Settings

Note: For OpenCode, only the official OpenCode-managed providers (OpenCode Go, OpenCode Zen) are shown.

Why

Users need a visible place to confirm provider usage limits without digging through logs or backend state. This keeps the value available across sessions and makes the current limits easy to inspect from the UI.

UI Changes

The Settings panel now shows provider usage limits in the provider section.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Provider status checks now spawn PTY/CLI probes and merge live rate-limit events into published snapshots; failures degrade to “unavailable” but add complexity and timing sensitivity around concurrent refreshes.

Overview
Adds provider subscription quota data to provider snapshots and surfaces it in the mobile Usage screen, with server-side probing and live refresh so limits stay current between status checks.

Server: Each major driver (Claude, Codex, Cursor, Grok, OpenCode) now attaches usageLimits during status probes—via Codex app-server account/rateLimits/read, Claude claude --print /usage, Cursor/Grok PTY /usage TUI automation, and OpenCode Go/Zen inventory fields. Optional PtyAdapter is threaded into Claude/Cursor/Grok driver checks so PTY-based probes work when the server layer provides it. applyUsageLimits on managed provider snapshots patches quota windows from runtime account.rate-limits.updated events (ProviderUsageLimitsIngestionLive), with epoch merging so in-flight probes do not overwrite fresher live updates.

Mobile: New Provider limits section on the usage route shows per-environment provider bars, reset dates, and notices (e.g. Grok free tier, unavailable API-key accounts).

Hygiene: Ignores .claude-work-test/ so Claude usage probe tests do not commit session files.

Reviewed by Cursor Bugbot for commit ea3c1ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider usage limits display to Usage page with CLI probing and runtime ingestion

  • Adds ServerProviderUsageLimits schema to server.ts with validated usage windows (session, weekly, monthly), percent clamping to 0–100, and forward-compatible decoding that drops invalid windows instead of failing the provider
  • Adds per-provider usage probes that launch provider CLIs in a PTY and parse their /usage or TUI output: Claude (claudeUsageProbe.ts, 15s timeout), Cursor (cursorUsageProbe.ts, 25s timeout), Grok (grokTuiUsageProbe.ts, 10s timeout), Codex via app server rate-limits API (codexUsageProbe.ts), and OpenCode managed providers (openCodeUsageLimits.ts)
  • Adds ProviderUsageLimitsIngestionLive layer in server.ts that subscribes to account.rate-limits.updated events, parses runtime telemetry via runtimeUsageLimits.ts, and calls applyUsageLimits on the provider instance to patch live snapshots
  • Updates makeManagedServerProvider.ts to expose applyUsageLimits, track per-window patch epochs, and preserve live-patched usage windows across concurrent refresh and enrichment
  • Renders usage bars with colored thresholds, reset dates, and tier notices in ProviderQuotaLimits.tsx on the web Usage page and in UsageRouteScreen.tsx on mobile
  • Improves PTY adapters: NodePtyAdapter.ts and BunPtyAdapter.ts now cache and replay exit events to late subscribers; Windows kill() uses taskkill /T /F for process-tree termination
  • Behavioral Change: NodePtyProcess constructor now requires a platform argument; Windows kill behavior switched from process.kill to taskkill with process.kill fallback. Provider snapshots now carry an optional usageLimits field; invalid usage windows are silently dropped during decode rather than failing the provider

Macroscope summarized ea3c1ce.


Open in Devin Review

@coderabbitai

coderabbitaiBot commented Apr 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee2bf19-dba6-4b48-840f-cced02ce3deb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 4, 2026
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

For now, I’ve added two images to illustrate the UI:

1. Free tier view

This screenshot reflects my current setup. I don’t have subscriptions to Codex or Claude Code, I’m using Copilot Pro (available to me as a student). It shows how the weekly usage limit appears in the interface.

Free Tier Screenshot

2. Pro tier (mocked example)

This second screenshot uses dummy data to demonstrate how the UI could look for users on a Pro plan (Codex or Claude Code). It includes both session-based limits and weekly limits for clarity.

Pro Tier Mock Screenshot

Comment threadapps/server/src/persistence/Layers/ProviderUsageLimits.ts Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:80515efa38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexProvider.ts Outdated
@macroscopeapp

macroscopeappBot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-provider usage-limit capability with new CLI/PTY probes, live runtime ingestion, shared snapshot concurrency logic, and web/mobile UI changes. Its broad production impact and several unresolved comments describing concrete runtime or display risks require human review.

You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/provider/codexAppServer.ts Outdated
@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Hey @juliusmarminge, could you take a look at this PR when you get a moment? Thanks!

@Marve10s

Copy link
Copy Markdown
Contributor

Randomly found this PR because wanted to add usage to T3. I think this is great as a user but also I think the UI should match the Codex App usage,it's minimal and clean, would be better than AI tabs
image

You would need to redo the settings button though,something Julius needs to approve. I genuinely think settings button should match Codex App, would love to open PR for that.

@Marve10s

Marve10s commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

A few concerns after checking the code:

  1. Codex can prefer stale persisted usage over freshly probed usage in the same refresh path.
    checkCodexProviderStatus() reads cachedUsageLimits first, then later resolves account state, but ultimately uses cachedUsageLimits ?? accountUsageLimits. That means a refresh can still emit an older persisted value even when the same refresh just fetched newer limits from the provider probe. If the goal is to show the freshest available snapshot, I think the precedence likely needs to be reversed, or persisted data should only be used when no fresh usage was fetched.
  2. The persisted cache looks too coarse-grained for account/config changes.
    The new table is keyed only by provider_name, and the repo API is also getByProvider only. That seems risky if the user changes auth state, swaps Codex homePath, changes binary path, or otherwise changes the effective account behind a provider. In those cases we can still surface previously persisted usage for the provider even though it may belong to a different account/config. Is that safe, or should this cache be scoped more tightly (for example by provider + account/config fingerprint) or suppressed when provider health/auth no longer matches the cached source?
  3. A usage-limit update currently looks like it can trigger full provider refresh work.
    ProviderService persists account.rate-limits.updated, then ProviderRegistry listens to repository changes and calls refresh(change.provider). That refresh path eventually reruns the provider's checkProvider flow rather than just updating already-known snapshot state. For a quota-bar style UI update, this feels heavier than necessary and could add extra CLI churn to a hot path. Would it be simpler to keep usage attached to the provider snapshot/update flow directly instead of going through persistence -> repository stream -> provider refresh?

I can test further and commit to this PR if you're okay with it, @Aditya190803. Genuinely want to see this merged - it would be super useful

@juliusmarminge

Copy link
Copy Markdown
Member

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

@Marve10s

Copy link
Copy Markdown
Contributor

this is on my list to review still! Just dealing with some larger prep work so haven't had time yet.

As for the "it must be more visible and sjhould be 1:1 like codex app":

How often do you guys check your limits to warrant it being one click ??? I check it at most a few times a week, so hiding it in settings next to the provider status is fine!

I've been checking usage a lot in Codex - a couple of times daily since I was on the $20 plan with 2x limits till April, so I was curious how it was going. My take is that the current settings are genuinely fine, but I think it'll get crowded soon. With usage, OpenCode and Cursor support, and I'm sure there are PRs that extend the settings further it's going to contain a lot of content before long.
Codex shows a small tab with everything you need: usage, general info, and language (something I'd tuck away in settings). I think the Cursor settings approach is a good reference here too.

image

@juliusmarminge

Copy link
Copy Markdown
Member

Yes when the single settings page gets too large we'll split it to subpages. We already did the work adding the sidebar when adding the archive

@juliusmarminge

Copy link
Copy Markdown
Member

can't see claude limits:
CleanShot 2026-04-06 at 15 21 34@2x

@juliusmarmingejuliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

implementation seems way overcomplicated.

why cache the data? the provider check runs once per minute. we can get fresh data on every tick?

how i imaagined this working:

  • extend the checkProvider probe in ServerProvider to include a new usageLimits property.
  • on the auth check probes (app server / claude), extract usage data
  • stream it down to client as part of the normal provider snapshot
  • render the UI on settings page

given i haven't looked into exactly what's possible to probe and not, why is this PR so much more than that?

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 7, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts
Comment threadapps/server/src/server.ts
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from f9aabcd to 171df70CompareApril 17, 2026 06:07
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 17, 2026
Comment threadapps/server/src/provider/codexAppServer.ts Outdated
Comment threadapps/server/src/provider/Layers/ClaudeProvider.ts Outdated
@Aditya190803
Aditya190803force-pushed the feat/provider-usage-limits branch from 036a9b9 to ed12bb8CompareApril 17, 2026 07:18
Replay last-exit on a microtask so Terminal Manager can assign session.process first. On Windows, kill the PTY tree with taskkill so cmd.exe-launched CLI children do not leak.
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment threadapps/server/src/terminal/NodePtyAdapter.ts
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
if (isGrokFreeTier(provider)) {
return GROK_FREE_TIER_USAGE_MESSAGE;
}
if (!provider.usageLimits) return "Usage data unavailable";

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.

Dropped usage windows leave empty tiles

Low Severity

Forward-compatible decoding now drops invalid or unknown usage windows instead of failing the provider, which can leave usageLimits.available true with an empty windows array. providerQuotaNotice only fills the tile body when usageLimits is missing, so those snapshots render a title with no bars and no message on web and mobile.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit fdc78fb. Configure here.

return {
models,
...(auth ? { auth } : {}),
} satisfies GrokAcpDiscoveryResult;

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.

Grok auth probe can discard models

Medium Severity

The Grok auth subscription probe still runs inside the outer ACP discovery timeout, so a slow acp.start() can leave too little budget for auth/check_subscription. When that outer timer fires, the already-built model catalog is thrown away and the provider is reported as a startup timeout instead of ready with unknown auth.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

Effect.catchCause((cause) =>
Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(Option.none()),
),
);

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.

Codex rate-limit interrupt drops snapshot

Medium Severity

The Codex rate-limit read rethrows interrupts, so an outer probe timeout that fires during account/rateLimits/read can still fail the whole app-server snapshot. Account, models, and skills that were already fetched are then discarded as a provider timeout instead of publishing with usage degraded.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 574252c. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Follow-up stacked on this PR: remaining session/weekly usage in the chat box, current provider only.

Stacked PR: #8445

  • Opt-in via Settings → General → Show usage in chat (default off)
  • Hidden until the thread has sent its first message
  • Bars go in the context-window hover when that ring exists; otherwise a compact percent next to send
  • Does not mix quota into the context-window ring number

Settings

Show usage in chat setting

Chat hover

Session and weekly usage in the context-window hover

? window.windowDurationMins
: reported.length > 1 && index === 0
? CODEX_SESSION_WINDOW_DURATION_MINS
: CODEX_WEEKLY_WINDOW_DURATION_MINS;

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.

Codex partials mislabeled as weekly

Medium Severity

Live Codex account.rate-limits.updated payloads reuse the full-snapshot resolver, which treats a lone duration-less primary window as weekly. Codex notifications are documented as partial, and mergeUsageLimitWindows upserts by kind, so a session-only patch can overwrite the weekly bar and leave the session bar stale during a turn.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e6ed20c. Configure here.

@derektrimm

Copy link
Copy Markdown

We hit a few landmines building the usage-limits implementation in #5739 that this PR's data paths will also cross, so passing them along to save the rediscovery:

  1. Claude reports utilization in two units, 100x apart. The streamed rate-limit events (and the anthropic-ratelimit-unified-*-utilization response headers they are built from) are a 0-1 fraction; GET /api/oauth/usage is 0-100. I verified this directly: hitting /v1/messages with a Claude Code OAuth token and calling the usage endpoint in the same second gave header 7d-utilization: 0.11 vs endpoint "seven_day": { "utilization": 12.0 }. Since this PR refreshes usageLimits from live session events on top of probe-populated snapshots, merging event values without scaling renders the binding window ~100x too small. This exact bug shipped on our branch and was caught by a user in the wild (see the report from Moinax in the feat(usage): show 5h/weekly rate limits on the usage page and sidebar hover #5739 thread). Worth confirming which unit the scraped /usage output uses before mixing the two sources.

  2. A streamed rate-limit event only names the currently binding window, never the full set. If the event-driven refresh replaces the snapshot's window list rather than merging by window key, every non-binding window (weekly, model-scoped) vanishes until the next full probe. We ended up pulling the full set separately (throttled) for this reason.

  3. Limits belong to the account, not the provider type. Two Claude provider instances signed into different accounts have independent windows; keying the cache or snapshot by provider type makes one account's numbers silently overwrite the other's. That was the whole of fix(usage): attribute account limits per provider instance #7133 on our branch.

  4. Treat the window set as data, not schema. Codex has shipped and then paused its 5-hour window, and Claude adds model-scoped weeklies (e.g. a "Fable"-scoped window) that come and go. Hardcoding the expected windows per provider breaks silently when the set changes upstream.

Happy to share the test fixtures we built for these cases if useful.

…imits
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeProvider.ts
#	apps/server/src/provider/makeManagedServerProvider.ts
#	apps/web/src/timestampFormat.test.ts

@macroscopeappmacroscopeappBot left a comment

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.

One finding in the in-scope web files: an empty provider tile can render when a usage snapshot decodes as available with zero windows. Everything else (bar geometry/role="progressbar"/motion-reduce matching ContextWindowMeter, locale-consistent reset formatting via timestampFormat.ts, indicatorBackground matching the page surface) is consistent with the existing system.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/ProviderQuotaLimits.tsx Outdated
Comment threadapps/server/src/provider/providerUsageLimits.ts Outdated
Aditya190803and others added 2 commits August 30, 2026 22:24
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

@cursorcursorBot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 32 total unresolved issues (including 31 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

return undefined;
}
if (duration > 30 * 24 * 60) {
return "monthly";

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.

Monthly windows classified as weekly

Medium Severity

windowKindFromDuration treats a window as monthly only when duration is strictly greater than 30 days. Codex Free/Go and Cursor both stamp exactly 30 days (43200 minutes), so those windows classify as weekly. Codex then labels the bar Weekly instead of Monthly.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit ea3c1ce. Configure here.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

Quick update:

  1. Fixed the reset dates, which were previously only shown for the week. They’re now shown separately for both session and week.
  2. For those who wanted usage displayed in the chat box, check out this PR: #8445

I wanted to make this a stacked PR, but couldn’t get that working, so I opened a new PR instead. I didn’t close this one since quite a few people are already following it.

@Aditya190803

Copy link
Copy Markdown
ContributorAuthor

It is a Opt in Feature

@derektrimm

Copy link
Copy Markdown

Thanks for the update, and for how much of the earlier list you've taken on - I went back through the current diff (plus the pipeline copy in #8445) and most of it is genuinely handled now. Wanted to close the loop properly:

  1. Unit scaling - handled for the streamed events, and the tests covering both directions are much appreciated. One small heads-up for the future: the <= 1 check is a magnitude heuristic rather than a typed unit, so a genuine sub-1 percent value (0.4% early in a fresh window) would render as 40%, and utilization: 1 is technically ambiguous (100% on the fraction scale, 1% on the percent scale). Since the OAuth endpoint and rate-limit headers aren't ingested yet, this can't bite today - but if a later change adds them, this seam is where the 100x bug would sneak back in. A short comment noting the ambiguity is probably all it needs for now.

  2. Merge vs replace - this is handled really well. The upsert semantics, carrying sparse fields forward, the per-window epochs guarding against a slow probe reverting a live patch, and the unavailable-probe protection are all exactly the shape we wished we'd built the first time. Nice work.

  3. Per-instance keying - handled. Ingestion routes by providerInstanceId into the instance's own snapshot, and the UI keys by instanceId everywhere I looked. Two same-type accounts can't cross-contaminate.

  4. Window set as data - this is the one I'd still love to see before merge, and the code is already halfway there: the renderers are fully data-driven (windows.map), so only the contract and merge key need to open up. Right now kind is a closed enum and mergeUsageLimitWindows keys its Map on kind, so two real windows of the same kind can never coexist - the comment on CLAUDE_WINDOW_BY_RATE_LIMIT_TYPE says it best ("would all collapse onto the same 'weekly' slot and fight over it"). That means model-scoped weeklies stay invisible, and any future window under 7 days gets labeled "session" and collides with the real one. A possible shape: give each window an opaque stable key (the provider's own identifier, e.g. the rate_limit_type string, falling back to label), merge on that key, and derive display order from windowDurationMins. Then provider window changes become a data change instead of a code change.

One perf observation on the #8445 copy of the pipeline, offered in the spirit of this repo's performance bar: every account.rate-limits.updated event currently republishes the full ServerProvider snapshot (models, skills, slash commands) to every connected client, and the dedupe guard in applyUsageLimits never gets a chance to fire - applyRuntimeUsageLimits stamps a fresh checkedAt on every event, so the value always differs even when every percentage is identical. Comparing just the window fields and keeping the previous checkedAt when nothing changed would make no-op events free on the wire.

Happy to share our test fixtures for the same-kind-window and unit-mismatch cases if they'd save you time - and thanks again for pushing this feature forward, a lot of people clearly want it.

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

11 participants

@Aditya190803@Marve10s@juliusmarminge@brrock@xPokerr@dtown123@jo-chemla@t3dotgg@greyxp1@syrok0010@derektrimm