feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg
, '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(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode - #8515

Closed
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits
Closed

feat(usage): add subscription limit reporting for Claude, Codex, Grok, and OpenCode#8515
AhmedShareef wants to merge 10 commits into
pingdotgg:mainfrom
AhmedShareef:feat/usage-limits

Conversation

@AhmedShareef

@AhmedShareefAhmedShareef commented Aug 28, 2026

Copy link
Copy Markdown

The usage page showed transcript-based activity but nothing about how much of a subscription's rate limits were left, so users driving agents all day had no way to see a window running hot before hitting it.

This adds a Limits view to the usage page, backed by a new UsageLimitsService on the server that reads each provider's local credentials/telemetry and reports subscription rate-limit windows over a new usageLimits contract:

  • Claude: limit tracking from Claude Code's local state, plus the service, contract, and Limits UI foundation.
  • Codex: window reporting, including auth handling and fractional rate-limit values.
  • Accounts: providers are distinguished by account email, so multiple logins don't collapse into one card.
  • Grok: OIDC billing windows, including omitted-zero usage handling.
  • OpenCode: Zen subscription rate windows.

Each provider is a separate commit and reviewable on its own. Parsers are defensive and covered by focused tests (215 passing across apps/server/src/usage/). Providers without support render as placeholders, so the view degrades cleanly.

Note

Draft: before/after screenshots and a short recording of the Limits view are pending; will attach before marking ready.

Built by Claude (Fable 5) via Claude Code.

Note

Add subscription limit reporting for Claude, Codex, Grok, and OpenCode

  • Introduces UsageLimitsService and dedicated parser modules to read auth state and rate-limit windows from Claude, Codex, Grok, and OpenCode CLI tools.
  • Adds the server.getUsageLimits WebSocket RPC and a useUsageLimits React hook to fetch and aggregate provider limits across environments.
  • Adds a "Limits" view toggle to the Usage page, rendering per-provider utilization bars, reset countdowns, and availability states.
  • Risk: UsageLimitsService spawns external processes (security keychain tool on macOS, codex app-server) with bounded scopes and timeouts; ensure process cleanup handles edge cases without leaking.
📊 Macroscope summarized c92b495. 17 files reviewed, 9 issues evaluated, 0 issues filtered, 9 comments posted

🗂️ Filtered Issues

- Expose usage-limits RPC with defensive Claude OAuth parsing
- Add Usage/Limits dashboard with refresh and reset countdowns
- Document subscription limit behavior
- Read Codex rate limits through its app server
- Add auth parsing, plan labels, window mapping, and tests
- Read account authentication state alongside usage windows
- Parse raw rate-limit responses and prioritize account-wide buckets
- Fetch and expose Claude and Codex account emails
- Group and label usage cards by account identity
- Extend usage contracts and parser tests
- Read Grok OIDC billing windows and display them in Limits
- Add defensive parsers, tests, contracts, and user documentation
- Treat missing proto3 usage percentages as zero for valid credits documents
- Add regression coverage for zero-usage billing windows
- Parse OpenCode credentials and Zen rate windows
- Show OpenCode limits across contracts, UI, and docs
- Add defensive parser tests
- Replace the usage breadcrumb with an accessible heading and inline date range
@coderabbitai

coderabbitaiBot commented Aug 28, 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: 5f40ac06-1d61-494a-9198-53139d3695fa

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

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:XXL 1,000+ changed lines (additions + deletions). labels Aug 28, 2026

@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 convention issue found: the new UsageLimitsService reads process.env directly instead of acquiring the HostProcessEnvironment reference from the Effect environment, hiding an environment dependency in a module global. Everything else (module order, inline Context.Service interface, make/layer/layerTest exports, dependency acquisition via yield*, namespace imports, in-band failure modelling) matches the repository's Effect service conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated

@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.

Two consistency findings in the new Limits view. Both are small, local fixes.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/usage/usageProviders.ts Outdated
Comment threadapps/web/src/components/usage/UsageLimitsContent.tsx Outdated
Comment threadapps/server/src/usage/usageLimitsCodex.ts Outdated
Comment threaddocs/user/usage.md Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
Comment threadapps/server/src/usage/UsageLimitsService.ts Outdated
}
}

return GROK_DEFAULT_PROXY_BASE_URL;

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.

🟠 Highusage/usageLimitsGrok.ts:107

When a user configures a team proxy only through [endpoints].cli_chat_proxy_base_url and models_cache.json is absent or stale, resolveGrokProxyBaseUrl returns GROK_DEFAULT_PROXY_BASE_URL, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return null when it cannot be parsed instead of falling back to the default.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/usageLimitsGrok.ts around line 107:
When a user configures a team proxy only through `[endpoints].cli_chat_proxy_base_url` and `models_cache.json` is absent or stale, `resolveGrokProxyBaseUrl` returns `GROK_DEFAULT_PROXY_BASE_URL`, so the caller sends the team-scoped bearer to the public endpoint. The resolver never reads or receives the CLI config value; include that endpoint in resolution and return `null` when it cannot be parsed instead of falling back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in 876c8e2: a cached origin that is present but unparseable now fails closed instead of falling back to the public default. The config-file-only case (proxy set via [endpoints] cli_chat_proxy_base_url with no models cache yet) is deliberately left on the default: resolving it would mean re-parsing the CLI's TOML config (new dependency, duplicated CLI internals) to cover a transient state that ends the moment the CLI writes its models cache — and the bearer is an xAI credential reaching xAI's own default endpoint, not a third party. The resolver's doc comment now states this limitation explicitly.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/usage/usageLimitsGrok.ts
Comment threadapps/server/src/usage/usageLimitsOpenCode.ts Outdated
Comment threadapps/server/src/usage/usageLimitsClaude.ts
…ides
- Read env through HostProcessEnvironment like the sibling UsageService
- Expand and resolve GROK_HOME/GROK_AUTH_PATH overrides before use
- Honor ambient CODEX_HOME in the auth pre-check to match the app server
- Report a settings-read failure as unavailable instead of unsupported
- Fail closed on a malformed cached Grok proxy origin
- Survive overflowing Codex reset instants, invalid Claude currency codes,
array-shaped OpenCode usage, and empty Grok period objects
- Use the Button primitive for the refresh action like the usage header
- Track the runtime contrast adjustment for the OpenCode series color
Comment on lines +574 to +575
const state = parseOpenCodeAuthState(injected);
if (state !== null) return state;

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.

🟡 Mediumusage/UsageLimitsService.ts:574

When OPENCODE_AUTH_CONTENT is a valid document with no recognized credential, readOpenCodeAuthState falls through to the on-disk auth.json and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including null, so a successfully parsed injected document suppresses disk auth.

- if (state !== null) return state;+ return state;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/usage/UsageLimitsService.ts around lines 574-575:
When `OPENCODE_AUTH_CONTENT` is a valid document with no recognized credential, `readOpenCodeAuthState` falls through to the on-disk `auth.json` and may report/use a stale Zen key that OpenCode is not using. Return the parsed injected state directly, including `null`, so a successfully parsed injected document suppresses disk auth.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping #1732 as the provider-limit source, #8445 as its separate composer follow-up, and #8456 for OpenCode reporting. This branch is an alternate 22-file service, and keeping it would split parser, cache, and contract ownership.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
@AhmedShareef
AhmedShareef deleted the feat/usage-limits branch August 29, 2026 05:47
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.

2 participants

@AhmedShareef@t3dotgg