Add Claude and Codex token usage dashboard#1039
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds Codex and Claude quota sampling, account discovery, TTL-persisted snapshots, IPC/preload bindings, renderer mirroring, new Usage Dashboard components (ClaudePlanUsage, CodexPlanUsage), modal integration, and expanded tests. ChangesCodex and Claude Usage Quota Dashboard
Sequence DiagramsequenceDiagram
participant App as App Startup
participant Startup as runCodexUsageSampling
participant Discover as discoverCodexHomes
participant Sampler as sampleCodexUsage
participant Store as codexUsageStore
participant IPC as IPC Handler
participant Renderer as Renderer Store
participant UI as CodexPlanUsage
App->>Startup: trigger sampling
Startup->>Discover: enumerate CODEX_HOME dirs
Discover-->>Startup: list of homes
Startup->>Sampler: sampleCodexUsage per target
Sampler->>Sampler: read auth.json -> call WHAM usage
Sampler-->>Startup: CodexUsageSnapshot
Startup->>Store: setCodexUsageSnapshot(snapshot)
Store->>Store: persist non-expired snapshots
Renderer->>IPC: getCodexUsageSnapshots()
IPC->>Store: getAllCodexUsageSnapshots()
Store-->>IPC: non-expired snapshots
IPC-->>Renderer: sanitized snapshot map
Renderer->>Renderer: useCodexUsageStore.refresh()
Renderer-->>UI: snapshots/loaded/refreshing
UI->>UI: render tabs and quota bars
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/renderer/components/UsageDashboard/CodexPlanUsage.tsx (1)
512-518: 💤 Low valueMinor: CSS class name inconsistency.
The class
claude-plan-refresh-sweepappears to be copy-pasted fromClaudePlanUsage. Consider renaming tocodex-plan-refresh-sweepfor consistency, though this won't affect functionality if the CSS is globally defined.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/UsageDashboard/CodexPlanUsage.tsx` around lines 512 - 518, In CodexPlanUsage, the span uses the CSS class name "claude-plan-refresh-sweep" which is inconsistent with this component; rename that class to "codex-plan-refresh-sweep" in the span inside the CodexPlanUsage component and update any corresponding CSS/SCSS selectors (or utility styles) that target "claude-plan-refresh-sweep" so styles apply under the new "codex-plan-refresh-sweep" name to keep naming consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/agents/codex-usage-sampler.ts`:
- Around line 161-169: The parseWindow function currently accepts NaN/Infinity
because it only checks typeof window.used_percent === 'number'; update the
validation in parseWindow (used_percent) to reject non-finite values by using
Number.isFinite(window.used_percent) (or equivalent) before creating the
CodexUsageSnapshot['session'] object, so only finite numeric percent values are
persisted; keep the existing parseResetAt(resetsAt) check and return null on
invalid percent.
In `@src/main/agents/codex-usage-startup.ts`:
- Around line 84-91: Guard against non-string CODEX_HOME values before using
path normalization: ensure merged.CODEX_HOME is a string (e.g., coerce or
validate) before assigning codexHome and calling resolveCodexHomeKey. In the
block that computes sessionEnvVars, merged, codexHome and codexHomeKey, validate
merged.CODEX_HOME (from session.customEnvVars/agentLevelEnvVars) and fallback to
path.join(os.homedir(), '.codex') if it's missing or not a string, then pass
that safe string into resolveCodexHomeKey to avoid throws from invalid types.
- Around line 130-148: The current Promise.all over
Array.from(targetsByKey.values()).map(...) lets any thrown error (e.g., from
sampleCodexUsage or setCodexUsageSnapshot) reject the whole batch; change this
so each account is isolated by catching errors per-target—either replace
Promise.all with Promise.allSettled and handle rejected results, or wrap the
body of the map in an outer try/catch that logs failures for that specific
target (use target.codexHomeKey and errors) while allowing other tasks to
continue; keep the existing logger usage and
setCodexUsageSnapshot/sampleCodexUsage calls but ensure failures do not
propagate to cancel the overall Promise.all.
---
Nitpick comments:
In `@src/renderer/components/UsageDashboard/CodexPlanUsage.tsx`:
- Around line 512-518: In CodexPlanUsage, the span uses the CSS class name
"claude-plan-refresh-sweep" which is inconsistent with this component; rename
that class to "codex-plan-refresh-sweep" in the span inside the CodexPlanUsage
component and update any corresponding CSS/SCSS selectors (or utility styles)
that target "claude-plan-refresh-sweep" so styles apply under the new
"codex-plan-refresh-sweep" name to keep naming consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 317dac78-181a-41bd-90ad-b61d49687615
📒 Files selected for processing (20)
src/__tests__/main/agents/codex-usage-sampler.test.tssrc/__tests__/main/ipc/handlers/agents.test.tssrc/__tests__/main/preload/agents.test.tssrc/__tests__/renderer/components/UsageDashboard/ClaudePlanUsage.test.tsxsrc/__tests__/renderer/components/UsageDashboard/CodexPlanUsage.test.tsxsrc/__tests__/renderer/components/UsageDashboard/UsageDashboardModal.test.tsxsrc/__tests__/setup.tssrc/main/agents/claude-usage-startup.tssrc/main/agents/codex-usage-sampler.tssrc/main/agents/codex-usage-startup.tssrc/main/ipc/handlers/agents.tssrc/main/preload/agents.tssrc/main/stores/codexUsageStore.tssrc/renderer/components/UsageDashboard/ClaudePlanUsage.tsxsrc/renderer/components/UsageDashboard/CodexPlanUsage.tsxsrc/renderer/components/UsageDashboard/UsageDashboardModal.tsxsrc/renderer/components/UsageDashboard/index.tssrc/renderer/global.d.tssrc/renderer/stores/claudeUsageStore.tssrc/renderer/stores/codexUsageStore.ts
Greptile SummaryThis PR adds provider-specific quota tabs ("Anthropic Usage" and "Codex Usage") to the Usage Dashboard, replacing the previously removed generic "Claude Max Plan Usage" panel in the Overview. Quota tabs are gated behind the
Confidence Score: 4/5Safe to merge; the new quota tabs are additive and gated behind an existing feature flag, so they cannot affect users who have not opted in. The data flow is well-isolated and all test suites pass. Two areas warrant a follow-up: the discovery helpers call synchronous fs APIs inside async IPC handlers, and the auto-refresh interval resets after every refresh cycle rather than firing on a fixed clock — neither breaks the feature but both can surprise users. src/main/ipc/handlers/agents.ts (sync fs calls in IPC handlers), src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx and CodexPlanUsage.tsx (auto-refresh timer behavior) Important Files Changed
Sequence DiagramsequenceDiagram
participant FS as Auth/Config Files
participant Sampler as Main: Sampler
participant Store as Main: Snapshot Store
participant IPC as IPC Handler
participant Preload as Preload Bridge
participant RStore as Renderer Store
participant UI as UsageDashboard
UI->>RStore: isOpen - refresh()
RStore->>Preload: getClaudeUsageSnapshots() / getCodexUsageSnapshots()
Preload->>IPC: IPC invoke
IPC->>Store: getAllSnapshots() prunes expired
Store-->>IPC: sanitized snapshots
IPC-->>Preload: Record of key to snapshot
Preload-->>RStore: snapshots
RStore-->>UI: store update - tabs appear
Note over UI: Provider tab visible only if usageStats enabled AND snapshot has valid quota window
UI->>Preload: Refresh button
Preload->>IPC: IPC invoke codex:usage:refresh-all
IPC->>Sampler: runCodexUsageSampling()
Sampler->>FS: discoverCodexHomes() reads auth.json
FS-->>Sampler: access_token
Sampler->>Sampler: fetch WHAM quota endpoint
Sampler->>Store: setCodexUsageSnapshot sanitized
IPC-->>Preload: refreshed count
UI->>RStore: refresh() re-fetch snapshots
Reviews (1): Last reviewed commit: "feat: add Claude and Codex token usage d..." | Re-trigger Greptile |
|
Hey @felipeggv — thanks a lot for this! 🎉 The provider-specific quota tabs are a really nice direction, the feature gating + sanitized-snapshot data flow is clean, and the test coverage across empty/pending/authenticated/unauthenticated/refresh states is excellent. No merge conflicts and it sits cleanly on I went through the Greptile and CodeRabbit reviews and verified each finding against the code. A few are worth addressing before we merge: Worth fixing
Minor / nits
Items 4–6 are optional polish, but I'd love to see 1–3 addressed (especially the interval bug). Let me know if any of these don't hold up on your end — happy to take another look once updated. Thanks again! 🙏 |
|
Addressed the review feedback in commit What changed:
Validation run before push:
Local full test result from the pre-push hook: 937 test files, 29,565 tests passed, 108 skipped. GitHub CI is currently running on the updated head. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/agents/codex-usage-startup.ts (1)
140-154:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPer-target error handling should distinguish recoverable vs unexpected failures.
The current catch logs and suppresses everything (including unexpected runtime faults). Keep recoverable sampling failures non-fatal, but send unexpected ones to Sentry and rethrow.
Suggested patch
Array.from(targetsByKey.values()).map(async (target) => { try { const snapshot = await sampleCodexUsage({ codexHome: target.codexHome }); setCodexUsageSnapshot(snapshot); logger.info('Stored Codex usage snapshot', LOG_CONTEXT, { codexHomeKey: snapshot.codexHomeKey, authState: snapshot.authState, sessionPercent: snapshot.session?.percent, weeklyPercent: snapshot.weekly?.percent, }); } catch (err) { - logger.warn('Failed to sample Codex usage snapshot', LOG_CONTEXT, { - codexHomeKey: target.codexHomeKey, - error: err instanceof Error ? err.message : String(err), - }); + if (isExpectedCodexUsageError(err)) { + logger.warn('Failed to sample Codex usage snapshot', LOG_CONTEXT, { + codexHomeKey: target.codexHomeKey, + error: err instanceof Error ? err.message : String(err), + }); + return; + } + captureException(err, { + extra: { codexHomeKey: target.codexHomeKey, phase: 'runCodexUsageSampling.sample' }, + }); + throw err; } }) );As per coding guidelines, "Do not silently swallow errors ... Handle expected/recoverable errors explicitly ... For unexpected errors, re-throw them to allow Sentry to capture them."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/agents/codex-usage-startup.ts` around lines 140 - 154, The catch block around sampleCodexUsage currently swallows all errors; change it to distinguish recoverable sampling failures from unexpected ones by implementing/using an isRecoverableSampleError(err) check (or inspect err.name/err.code) and only log-and-suppress when that returns true, otherwise call Sentry.captureException(err) and rethrow the error; update the block that calls sampleCodexUsage/ setCodexUsageSnapshot and the logger.warn path to use the recoverable check and to include Sentry.captureException + throw for unexpected errors, referencing sampleCodexUsage, setCodexUsageSnapshot, logger.warn/info, LOG_CONTEXT and target.codexHomeKey so the fix is easy to locate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/agents/codex-usage-startup.ts`:
- Around line 64-66: The catch block after await
fs.promises.access(path.join(codexHome, 'auth.json'), fs.constants.R_OK) should
only swallow expected ENOENT/EACCES errors; update the catch to inspect the
caught error and if its code is 'ENOENT' or 'EACCES' continue (treat as
missing/unreadable auth.json), but for any other error call
captureException/captureMessage from src/utils/sentry.ts and rethrow the error
so it surfaces; reference the codexHome/auth.json access check and the
surrounding discovery loop when making this change.
---
Outside diff comments:
In `@src/main/agents/codex-usage-startup.ts`:
- Around line 140-154: The catch block around sampleCodexUsage currently
swallows all errors; change it to distinguish recoverable sampling failures from
unexpected ones by implementing/using an isRecoverableSampleError(err) check (or
inspect err.name/err.code) and only log-and-suppress when that returns true,
otherwise call Sentry.captureException(err) and rethrow the error; update the
block that calls sampleCodexUsage/ setCodexUsageSnapshot and the logger.warn
path to use the recoverable check and to include Sentry.captureException + throw
for unexpected errors, referencing sampleCodexUsage, setCodexUsageSnapshot,
logger.warn/info, LOG_CONTEXT and target.codexHomeKey so the fix is easy to
locate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 979003f5-79f4-471a-9cb7-33cfc6f4e30c
📒 Files selected for processing (9)
src/__tests__/main/agents/codex-usage-sampler.test.tssrc/__tests__/main/agents/codex-usage-startup.test.tssrc/main/agents/claude-usage-startup.tssrc/main/agents/codex-usage-sampler.tssrc/main/agents/codex-usage-startup.tssrc/main/ipc/handlers/agents.tssrc/renderer/components/UsageDashboard/ClaudePlanUsage.tsxsrc/renderer/components/UsageDashboard/CodexPlanUsage.tsxsrc/renderer/index.css
|
Follow-up completed in commit This addresses the additional CodeRabbit finding on Codex home discovery:
Validation:
All review threads are resolved. The PR is clean to merge. |
Summary
This PR adds provider-specific quota usage surfaces to the existing Usage Dashboard for Claude/Anthropic and Codex, without bringing back the removed generic "Claude Max Plan Usage" panel in the Overview tab.
The dashboard now behaves like a cockpit for weekly quota limits: it shows quota bars only when the Usage and Stats Encore Feature is enabled and only when Maestro has useful cached quota data for that provider.
Why
Maestro already tracks rich usage analytics, but quota limits were not visible in a reliable, provider-specific way. The previous dashboard direction mixed quota UI into the main Overview and used provider/account naming that could be misleading.
This change moves quota monitoring into dedicated provider tabs:
Anthropic Usagefor Claude/Anthropic quota snapshots.Codex Usagefor Codex quota snapshots.That keeps the main analytics dashboard focused while still making quota pressure visible when the user has real quota data available.
User-facing behavior
Dynamic tabs
The Usage Dashboard builds the provider tabs dynamically:
Anthropic Usageappears only whenencoreFeatures.usageStatsis enabled and at least one Claude/Anthropic snapshot has useful quota window data.Codex Usageappears only whenencoreFeatures.usageStatsis enabled and at least one Codex snapshot has useful quota window data.Overview.This avoids showing a blank or misleading tab just because a provider exists.
Quota panels
Both provider panels show account-level quota rows using the same visual language:
The dashboard mounts provider panels with
autoRefresh={false}. Opening the Usage Dashboard refreshes only the renderer mirror from cached main-process state; it does not automatically launch provider samplers or run expensive provider commands.Feature gating
The quota tabs are gated behind the existing Usage and Stats Encore Feature. If
usageStatsis off, provider snapshots can exist in stores, but the Usage Dashboard will not expose the provider quota tabs.Data flow
flowchart LR A[Provider auth/config files] --> B[Main process sampler] B --> C[Sanitized quota snapshot store] C --> D[IPC/preload safe bridge] D --> E[Renderer usage store] E --> F[Usage Dashboard provider tab]In plain terms: main process reads sensitive provider-side files, strips the data down to safe quota metadata, and the renderer only receives sanitized snapshots.
Codex implementation
This PR adds a Codex quota path parallel to the existing Claude/Anthropic snapshot path:
CODEX_HOME/auth.jsonwhen available.The renderer never reads auth files or tokens directly.
Claude/Anthropic implementation
This PR keeps the upstream
rcdecision to remove the old "Claude Max Plan Usage" Overview panel, then adds a new provider-specific Anthropic tab instead.It also improves account discovery so multiple local Claude config directories can be represented when useful snapshots exist, while still avoiding backup/archive/stage/local scratch directories.
Main files changed
Main process
src/main/agents/codex-usage-sampler.tssrc/main/agents/codex-usage-startup.tssrc/main/stores/codexUsageStore.tssrc/main/agents/claude-usage-startup.tssrc/main/ipc/handlers/agents.tssrc/main/preload/agents.tsRenderer
src/renderer/components/UsageDashboard/UsageDashboardModal.tsxsrc/renderer/components/UsageDashboard/ClaudePlanUsage.tsxsrc/renderer/components/UsageDashboard/CodexPlanUsage.tsxsrc/renderer/stores/claudeUsageStore.tssrc/renderer/stores/codexUsageStore.tssrc/renderer/global.d.tsTests
src/__tests__/main/agents/codex-usage-sampler.test.tssrc/__tests__/main/ipc/handlers/agents.test.tssrc/__tests__/main/preload/agents.test.tssrc/__tests__/renderer/components/UsageDashboard/ClaudePlanUsage.test.tsxsrc/__tests__/renderer/components/UsageDashboard/CodexPlanUsage.test.tsxsrc/__tests__/renderer/components/UsageDashboard/UsageDashboardModal.test.tsxValidation
Executed successfully after rebasing on
origin/rc:git diff --checknpm run lint -- --pretty falsenpx vitest run src/__tests__/renderer/components/UsageDashboard/UsageDashboardModal.test.tsx src/__tests__/renderer/components/UsageDashboard/ClaudePlanUsage.test.tsx src/__tests__/renderer/components/UsageDashboard/CodexPlanUsage.test.tsx src/__tests__/main/agents/codex-usage-sampler.test.ts src/__tests__/main/ipc/handlers/agents.test.ts src/__tests__/main/preload/agents.test.tsnpm run buildnpm run testThe repository push hook also ran successfully before publishing the branch to the fork:
npm run format:check:allnpm run lintnpm run lint:eslintnpm run testNotes and risk
olive-nightstheme work from the branch.Summary by CodeRabbit
New Features
Bug Fixes
Tests