Skip to content

Add Claude and Codex token usage dashboard#1039

Open
felipeggv wants to merge 3 commits into
RunMaestro:rcfrom
felipeggv:feat/claude-codex-token-usage-dashboard
Open

Add Claude and Codex token usage dashboard#1039
felipeggv wants to merge 3 commits into
RunMaestro:rcfrom
felipeggv:feat/claude-codex-token-usage-dashboard

Conversation

@felipeggv
Copy link
Copy Markdown

@felipeggv felipeggv commented May 23, 2026

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 Usage for Claude/Anthropic quota snapshots.
  • Codex Usage for 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 Usage appears only when encoreFeatures.usageStats is enabled and at least one Claude/Anthropic snapshot has useful quota window data.
  • Codex Usage appears only when encoreFeatures.usageStats is enabled and at least one Codex snapshot has useful quota window data.
  • Empty, unauthenticated, or error-only snapshots do not create visible quota tabs.
  • If a provider tab disappears while selected, the dashboard falls back to 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:

  • Account selector pills.
  • Horizontal usage bars.
  • Reset-time labels.
  • Refresh button.
  • Configurable auto-refresh interval.
  • Optional "show all accounts" mode used by the dashboard tabs.

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 usageStats is 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]
Loading

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:

  • Main process discovers Codex auth/config context.
  • Main process reads CODEX_HOME/auth.json when available.
  • Main process requests quota metadata from the ChatGPT/Codex usage endpoint.
  • Main process stores sanitized Codex snapshots.
  • Renderer accesses snapshots through preload/IPC only.

The renderer never reads auth files or tokens directly.

Claude/Anthropic implementation

This PR keeps the upstream rc decision 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.ts
  • src/main/agents/codex-usage-startup.ts
  • src/main/stores/codexUsageStore.ts
  • src/main/agents/claude-usage-startup.ts
  • src/main/ipc/handlers/agents.ts
  • src/main/preload/agents.ts

Renderer

  • src/renderer/components/UsageDashboard/UsageDashboardModal.tsx
  • src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx
  • src/renderer/components/UsageDashboard/CodexPlanUsage.tsx
  • src/renderer/stores/claudeUsageStore.ts
  • src/renderer/stores/codexUsageStore.ts
  • src/renderer/global.d.ts

Tests

  • src/__tests__/main/agents/codex-usage-sampler.test.ts
  • src/__tests__/main/ipc/handlers/agents.test.ts
  • src/__tests__/main/preload/agents.test.ts
  • src/__tests__/renderer/components/UsageDashboard/ClaudePlanUsage.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/CodexPlanUsage.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/UsageDashboardModal.test.tsx

Validation

Executed successfully after rebasing on origin/rc:

  • git diff --check
  • npm run lint -- --pretty false
  • npx 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.ts
    • 6 files passed
    • 142 tests passed
  • npm run build
    • passed
    • emitted existing Vite/CSS/chunk-size warnings
  • npm run test
    • 935 files passed
    • 1 file skipped
    • 29,562 tests passed
    • 108 tests skipped

The repository push hook also ran successfully before publishing the branch to the fork:

  • npm run format:check:all
  • npm run lint
  • npm run lint:eslint
  • npm run test

Notes and risk

  • Codex quota metadata depends on the currently available ChatGPT/Codex usage endpoint. Failures are fail-closed: empty or error-only snapshots do not create a visible dashboard tab.
  • Provider secrets remain in main-process scope. Renderer receives sanitized quota snapshots only.
  • This PR intentionally excludes unrelated local olive-nights theme work from the branch.

Summary by CodeRabbit

  • New Features

    • Codex per-account quota tracking and dashboard view with progress bars, unauthenticated CTAs, manual/auto/periodic refresh, and visual busy state.
    • Claude account auto-discovery and integrated account tabs; new Anthropic and Codex usage tabs in the Usage Dashboard.
    • Renderer-store persistence and IPC endpoints to fetch/refresh provider usage snapshots.
  • Bug Fixes

    • Improved handling of invalid quota values so charts ignore non-finite data.
  • Tests

    • Added comprehensive tests covering sampling, discovery, stores, IPC wiring, and new dashboard components.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0860ee31-3ed6-43ed-a525-c6901f39c6d3

📥 Commits

Reviewing files that changed from the base of the PR and between 6530c90 and c54e0c8.

📒 Files selected for processing (2)
  • src/__tests__/main/agents/codex-usage-startup.test.ts
  • src/main/agents/codex-usage-startup.ts

📝 Walkthrough

Walkthrough

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

Changes

Codex and Claude Usage Quota Dashboard

Layer / File(s) Summary
Quota data model and main-process persistence
src/main/stores/codexUsageStore.ts
Defines Codex snapshot/window/additional-limit types, TTL constant, Electron Store persistence, set/get/clear helpers, CODEX_HOME key resolution, and test reset hook.
Codex usage sampling and parsing
src/main/agents/codex-usage-sampler.ts
Implements sampleCodexUsage: reads auth.json, calls WHAM usage endpoint with abort timeout, parses session/weekly/additional windows, normalizes resetsAt, extracts email from JWT, and classifies auth/error states.
Codex usage startup orchestration
src/main/agents/codex-usage-startup.ts
Discovers Codex homes, merges agent/session env vars into deduplicated sampling targets, applies fallback CODEX_HOME, concurrently samples per-target via sampleCodexUsage, and persists snapshots with per-target error handling.
Claude account discovery enhancement
src/main/agents/claude-usage-startup.ts
Adds discoverClaudeConfigDirs() to find ~/.claude-* config dirs, verify .claude.json readability, and augment manual sampling targets with CLAUDE_CONFIG_DIR entries.
IPC handlers, preload wiring, and type contracts
src/main/ipc/handlers/agents.ts, src/main/preload/agents.ts, src/renderer/global.d.ts
Registers IPC handlers: agents:getClaudeUsageAccountKeys, agents:getCodexUsageSnapshots, agents:getCodexUsageAccountKeys, codex:usage:refresh-all; extends preload API to expose these methods and re-exports CodexUsageSnapshot; updates renderer global types for richer snapshot schemas.
Renderer-side snapshot mirroring and store
src/renderer/stores/codexUsageStore.ts
Adds Zustand useCodexUsageStore with snapshots, loaded, refreshing, setSnapshots, refresh() (IPC-backed), and __resetForTests; exports helper to read current snapshots.
Claude plan usage dashboard component
src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx
New ClaudePlanUsage component: per-CLAUDE_CONFIG_DIR account discovery, merged env/session keys, tabbed/all-accounts views, threshold-colored progress bars, pending/unauthenticated branches, manual/auto/periodic refresh controls with visual busy gating.
Codex plan usage dashboard component
src/renderer/components/UsageDashboard/CodexPlanUsage.tsx
New CodexPlanUsage component: derives display names from CODEX_HOME keys, merges discovered/prop/session keys, renders authenticated quota bars/additional limits or unauthenticated/error/pending rows, supports refresh with visual throttling and optional auto/periodic sampling.
Usage dashboard modal tab wiring and view modes
src/renderer/components/UsageDashboard/UsageDashboardModal.tsx
Adds anthropic-usage and codex-usage view modes, quota-window validation helpers, dynamic tab gating based on feature flags and snapshot usefulness, store refresh on modal open, and render branches for provider usage components inside error boundaries.
Test infrastructure, mocks, and suites
src/__tests__/setup.ts, src/__tests__/main/agents/codex-usage-sampler.test.ts, src/__tests__/main/agents/codex-usage-startup.test.ts, src/__tests__/main/ipc/handlers/agents.test.ts, src/__tests__/main/preload/agents.test.ts, src/__tests__/renderer/components/UsageDashboard/*.test.tsx, src/renderer/components/UsageDashboard/index.ts, src/renderer/stores/claudeUsageStore.ts
Extends global test mocks with new IPC methods; adds Codex sampler/startup tests; updates IPC handler registration and preload API tests; adds comprehensive renderer tests for Claude/Codex UI behaviors, refresh wiring, accessibility assertions, and modal tab gating; exports new dashboard components from the barrel.

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

approved, ready to merge

Poem

🐰 A small hop, a sample run,

Codex and Claude now show their sun.
Quotas counted, tabs in view,
Snapshots cached and UI new.
Refresh, then dance — the dashboard's done!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the primary change: adding Claude and Codex token usage dashboard components to the Usage Dashboard interface.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

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.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/renderer/components/UsageDashboard/CodexPlanUsage.tsx (1)

512-518: 💤 Low value

Minor: CSS class name inconsistency.

The class claude-plan-refresh-sweep appears to be copy-pasted from ClaudePlanUsage. Consider renaming to codex-plan-refresh-sweep for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5ffa3ef and 87d777a.

📒 Files selected for processing (20)
  • src/__tests__/main/agents/codex-usage-sampler.test.ts
  • src/__tests__/main/ipc/handlers/agents.test.ts
  • src/__tests__/main/preload/agents.test.ts
  • src/__tests__/renderer/components/UsageDashboard/ClaudePlanUsage.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/CodexPlanUsage.test.tsx
  • src/__tests__/renderer/components/UsageDashboard/UsageDashboardModal.test.tsx
  • src/__tests__/setup.ts
  • src/main/agents/claude-usage-startup.ts
  • src/main/agents/codex-usage-sampler.ts
  • src/main/agents/codex-usage-startup.ts
  • src/main/ipc/handlers/agents.ts
  • src/main/preload/agents.ts
  • src/main/stores/codexUsageStore.ts
  • src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx
  • src/renderer/components/UsageDashboard/CodexPlanUsage.tsx
  • src/renderer/components/UsageDashboard/UsageDashboardModal.tsx
  • src/renderer/components/UsageDashboard/index.ts
  • src/renderer/global.d.ts
  • src/renderer/stores/claudeUsageStore.ts
  • src/renderer/stores/codexUsageStore.ts

Comment thread src/main/agents/codex-usage-sampler.ts
Comment thread src/main/agents/codex-usage-startup.ts
Comment thread src/main/agents/codex-usage-startup.ts
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 23, 2026

Greptile Summary

This 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 usageStats Encore feature and appear dynamically only when cached snapshots contain meaningful window data for that provider.

  • Main process gains a Codex sampling path (codex-usage-sampler.ts, codex-usage-startup.ts, codexUsageStore.ts) that reads CODEX_HOME/auth.json and requests quota metadata from the ChatGPT/Codex WHAM endpoint; auth tokens never cross the IPC boundary.
  • Renderer-side ClaudePlanUsage and CodexPlanUsage components render per-account quota bars with configurable auto-refresh, and UsageDashboardModal builds the provider tabs dynamically from store state on open.

Confidence Score: 4/5

Safe 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

Filename Overview
src/main/agents/codex-usage-sampler.ts New file: reads Codex auth.json, calls ChatGPT quota endpoint, returns sanitized CodexUsageSnapshot; token never leaves main process. Redundant clearTimeout in catch block (finally also clears it), but harmless.
src/main/agents/codex-usage-startup.ts New file: discovers CODEX_HOME directories, builds sampling targets from sessions + agent env vars + filesystem discovery, falls back to ~/.codex when Codex is detected but no homes found. Uses sync fs calls in discoverCodexHomes().
src/main/stores/codexUsageStore.ts New file: mirrors ClaudeUsage store pattern for Codex snapshots. TTL pruning on read and write, resolveCodexHomeKey normalizes paths via path.resolve, __resetForTests hook provided.
src/main/ipc/handlers/agents.ts Adds four new IPC handlers for Codex+Claude account key discovery and snapshot retrieval/refresh. The getClaudeUsageAccountKeys and getCodexUsageAccountKeys handlers call synchronous fs functions inside async handlers, blocking the main process event loop.
src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx New 677-line component: per-account Claude quota bars with sub-tabs, refresh button, and configurable auto-refresh interval. Periodic refresh interval restarts after every cycle due to handleRefresh dependency pattern.
src/renderer/components/UsageDashboard/CodexPlanUsage.tsx New 614-line component mirroring ClaudePlanUsage for Codex. Same periodic-interval restart issue. Renders email/planType metadata and handles missing/additional-limits gracefully.
src/renderer/components/UsageDashboard/UsageDashboardModal.tsx Adds dynamic Anthropic/Codex provider tabs gated on usageStats Encore feature and cached snapshot validity. Falls back to overview when active tab disappears. Lazy-loads snapshots on open.
src/renderer/stores/codexUsageStore.ts New renderer-side Zustand store mirroring claudeUsageStore shape. Atomic snapshot replacement, refreshing guard, loaded flag. Clean implementation.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (1): Last reviewed commit: "feat: add Claude and Codex token usage d..." | Re-trigger Greptile

Comment thread src/main/ipc/handlers/agents.ts Outdated
Comment thread src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx Outdated
@pedramamini
Copy link
Copy Markdown
Collaborator

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

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

  1. Auto-refresh interval restarts every cycle (ClaudePlanUsage.tsx ~L475 and the equivalent in CodexPlanUsage.tsx). The interval useEffect lists handleRefresh as a dep, but handleRefresh is a useCallback keyed on [refreshing, visualBusy] — both of which flip twice per refresh. So the timer is cleared and rebuilt from zero on every refresh, making it effectively "N minutes after the last refresh settled" rather than "every N minutes." Holding the latest callback in a useRef so the interval effect doesn't depend on handleRefresh is the usual fix.

  2. Per-account sampling failures can cancel the whole batch (codex-usage-startup.ts ~L131). The await sampleCodexUsage(...) call sits outside the inner try/catch, so an unexpected throw there would reject the whole Promise.all and skip the remaining accounts. The sampler guards its own paths well today so the practical risk is low, but wrapping the call (or switching to Promise.allSettled) would make multi-account sampling robust.

  3. Synchronous fs in discovery on the main thread (agents.ts ~L1502 → discoverClaudeConfigDirs() / discoverCodexHomes()). Both use fs.readdirSync + fs.accessSync, and they're reached from async IPC handlers, so they block the event loop while scanning the home dir. Porting to fs.promises keeps the UI responsive on slow/networked home directories.

Minor / nits

  1. parseWindow (codex-usage-sampler.ts ~L163) accepts NaN/Infinity since it only checks typeof === 'number'. A Number.isFinite() guard would match the sibling parser at L192 and keep bad percentages out of the snapshot/UI.
  2. buildTarget (codex-usage-startup.ts ~L89) trusts merged.CODEX_HOME as a string before path normalization — a small type guard avoids a throw on a corrupted stored env value.
  3. (Cosmetic) CodexPlanUsage.tsx reuses the claude-plan-refresh-sweep CSS class; renaming to codex-plan-refresh-sweep is purely for consistency.

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! 🙏

@felipeggv
Copy link
Copy Markdown
Author

Addressed the review feedback in commit 6530c903d.

What changed:

  1. Fixed the auto-refresh timer drift in both quota panels.

    • ClaudePlanUsage.tsx and CodexPlanUsage.tsx now keep the latest refresh callback in a ref, so the interval runs on a stable clock instead of restarting after every refresh state change.
  2. Made Codex multi-account sampling resilient per account.

    • codex-usage-startup.ts now isolates failures per target. If one Codex home throws during sampling or persistence, the other accounts continue and the failure is logged for that account.
  3. Moved quota account discovery off synchronous filesystem calls.

    • discoverClaudeConfigDirs() and discoverCodexHomes() now use fs.promises, and the async IPC handlers await them instead of blocking the main process while scanning home directories.
  4. Tightened Codex quota parsing.

    • parseWindow now rejects non-finite used_percent values with Number.isFinite, so NaN and Infinity do not enter persisted snapshots or the UI.
  5. Hardened Codex target building.

    • buildTarget now validates CODEX_HOME before path normalization and falls back to ~/.codex for corrupted/non-string stored env values.
  6. Cleaned up Codex refresh sweep naming.

    • The Codex panel now uses codex-plan-refresh-sweep, with shared CSS support alongside the Claude sweep class.

Validation run before push:

  • npx vitest run 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/agents/codex-usage-startup.test.ts src/__tests__/main/ipc/handlers/agents.test.ts src/__tests__/main/preload/agents.test.ts
  • npm run lint -- --pretty false
  • npm run lint:eslint
  • npm run build
  • pre-push hook: npm run format:check:all && npm run lint && npm run lint:eslint && npm run test

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 win

Per-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

📥 Commits

Reviewing files that changed from the base of the PR and between 87d777a and 6530c90.

📒 Files selected for processing (9)
  • src/__tests__/main/agents/codex-usage-sampler.test.ts
  • src/__tests__/main/agents/codex-usage-startup.test.ts
  • src/main/agents/claude-usage-startup.ts
  • src/main/agents/codex-usage-sampler.ts
  • src/main/agents/codex-usage-startup.ts
  • src/main/ipc/handlers/agents.ts
  • src/renderer/components/UsageDashboard/ClaudePlanUsage.tsx
  • src/renderer/components/UsageDashboard/CodexPlanUsage.tsx
  • src/renderer/index.css

Comment thread src/main/agents/codex-usage-startup.ts Outdated
@felipeggv
Copy link
Copy Markdown
Author

Follow-up completed in commit c54e0c833.

This addresses the additional CodeRabbit finding on Codex home discovery:

  • discoverCodexHomes() now only swallows expected auth.json access failures: ENOENT and EACCES.
  • Unexpected filesystem errors are reported through captureException with codexHome and authPath context, then rethrown so they remain visible instead of being silently hidden.
  • Added coverage in src/__tests__/main/agents/codex-usage-startup.test.ts for both expected skipped accounts and unexpected access failures.

Validation:

  • npx vitest run src/__tests__/main/agents/codex-usage-startup.test.ts: 4 tests passed.
  • Pre-push hook passed: npm run format:check:all && npm run lint && npm run lint:eslint && npm run test.
  • Full local test result: 937 test files, 29,573 tests passed, 108 skipped.
  • GitHub CI is green on head c54e0c833: lint-and-format, test, and CodeRabbit all passed.

All review threads are resolved. The PR is clean to merge.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants