Share one usage builder between Claude's OAuth and web paths - #209
Conversation
Claude's two sources each dropped what the other rendered. They were separate response types with separate snapshot builders, so every window Anthropic added had to be wired twice and never was. OAuth parsed a design window and never rendered it, parsed extra usage and threw the cost away. Web only ever read `seven_day_opus` for the model-specific slot, so an account with no Opus pool showed nothing where Sonnet belonged. Both now deserialize into `ClaudeUsageResponse` and build through `build_snapshot`, which maps every window once. The map-based deserializer that web needed for Anthropic's overlapping alias keys now covers OAuth too. Two behavior changes fall out of the unification: - OAuth's `seven_day_omelette` was aliased onto routines and rendered as "Daily Routines". It maps to the promotional window now, so it reads "Weekly promo" as it already did on web. - Web falls back to the usage payload's embedded extra usage when the dedicated overage endpoint fails, rather than reporting no cost. 807 rust / 474 tauri, clippy and fmt clean on both manifests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ceiling | 261538c | Commit Preview URL Branch Preview URL |
Aug 05 2026, 02:00 PM |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughClaude usage parsing and snapshot construction now use shared types across OAuth and web providers. The shared module handles payload variants, usage-window normalization, scoped limits, and extra-usage cost conversion. ChangesClaude usage normalization
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant ClaudeUsageFetcher
participant ClaudeUsageResponse
participant SnapshotBuilder
ClaudeUsageFetcher->>ClaudeUsageResponse: deserialize OAuth or web payload
ClaudeUsageResponse->>SnapshotBuilder: build_snapshot()
SnapshotBuilder-->>ClaudeUsageFetcher: normalized usage snapshot
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
rust/src/providers/claude/usage_api.rs (2)
290-309: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test that pins Opus precedence over Sonnet.
build_snapshotselectsseven_day_opusfirst and falls back toseven_day_sonnet.web_shape_keeps_sonnet_as_the_model_specific_windowsupplies only Sonnet, so a reversedor_elseorder would still pass. Add a case that supplies both windows.As per coding guidelines: "Add or extend focused Rust tests near the changed module; use deterministic samples or fixtures for parser and fetcher changes where practical."
💚 Proposed test
#[test] fn opus_wins_over_sonnet_for_the_model_specific_window() { let response: ClaudeUsageResponse = serde_json::from_str( r#"{ "five_hour": {"utilization": 12}, "seven_day_opus": {"utilization": 67}, "seven_day_sonnet": {"utilization": 45} }"#, ) .expect("response parses"); assert_eq!( snapshot(&response) .model_specific .expect("Opus model-specific window") .used_percent, 67.0 ); }🤖 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 `@rust/src/providers/claude/usage_api.rs` around lines 290 - 309, Add a focused test near web_shape_keeps_sonnet_as_the_model_specific_window that provides both seven_day_opus and seven_day_sonnet, then assert snapshot(&response).model_specific uses the Opus utilization value. Keep the existing Sonnet-only coverage unchanged and use deterministic JSON input.Source: Coding guidelines
168-207: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueName the window-length constants.
Some(10080)repeats five times andSome(300)once. Named constants make the five-hour and seven-day intent explicit and prevent a future edit from changing only one site.♻️ Proposed refactor
impl ClaudeUsageResponse { + const FIVE_HOUR_MINUTES: u32 = 5 * 60; + const SEVEN_DAY_MINUTES: u32 = 7 * 24 * 60; + pub(super) fn utilization_scale(&self) -> UtilizationScale {Then replace
Some(300)withSome(Self::FIVE_HOUR_MINUTES)and eachSome(10080)withSome(Self::SEVEN_DAY_MINUTES).🤖 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 `@rust/src/providers/claude/usage_api.rs` around lines 168 - 207, Define named window-length constants on the relevant implementation, such as FIVE_HOUR_MINUTES and SEVEN_DAY_MINUTES, then replace every Some(300) and Some(10080) passed to convert with the corresponding constant. Update all occurrences consistently, including the model-specific and auxiliary window conversions.
🤖 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 `@rust/src/providers/claude/web_api.rs`:
- Around line 343-351: Remove the embedded-payload fallback from the cost
selection in the surrounding provider response flow: update the expression using
ClaudeExtraUsage::cost_snapshot so it only uses the successful dedicated
endpoint result. Preserve the existing extra_usage assignment and
endpoint-failure fallback implemented earlier, and continue applying with_cost
only when the selected endpoint snapshot provides a cost.
---
Nitpick comments:
In `@rust/src/providers/claude/usage_api.rs`:
- Around line 290-309: Add a focused test near
web_shape_keeps_sonnet_as_the_model_specific_window that provides both
seven_day_opus and seven_day_sonnet, then assert
snapshot(&response).model_specific uses the Opus utilization value. Keep the
existing Sonnet-only coverage unchanged and use deterministic JSON input.
- Around line 168-207: Define named window-length constants on the relevant
implementation, such as FIVE_HOUR_MINUTES and SEVEN_DAY_MINUTES, then replace
every Some(300) and Some(10080) passed to convert with the corresponding
constant. Update all occurrences consistently, including the model-specific and
auxiliary window conversions.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 766e312c-0d93-4eb1-bcab-2890ee4ef21d
📒 Files selected for processing (4)
rust/src/providers/claude/mod.rsrust/src/providers/claude/oauth/mod.rsrust/src/providers/claude/usage_api.rsrust/src/providers/claude/web_api.rs
The fetch site already falls back to the usage payload's embedded extra usage when the dedicated overage endpoint fails, so a second fallback at the cost site only fires when that endpoint *succeeded* and reported `is_enabled: false`. That turned a deliberate "no overage" answer into a cost read from the stale embedded copy. Caught by CodeRabbit on #209. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Good catch — and it was worse than redundant. The endpoint-failure fallback already exists on Removed in 261538c. My PR description above was also wrong to list "web gained a cost fallback" as a behavior change; the fallback was already there, and the net change to web's cost path is now nil. 807 rust / 474 tauri, clippy and fmt clean. |
Closes SOU-546.
Claude's OAuth and web sources had drifted into two response types with two snapshot builders. Every window Anthropic adds has to be wired twice, and it never was — so each path dropped what the other rendered. One root cause, three symptoms.
What was broken
sevenDayDesign, withseven_day_oauth_appsaliased onto it) and then simply never read when the snapshot was built. Only routines made it intoextra_rate_windows.ExtraUsagestruct existed, deserialized correctly, and was discarded.ProviderFetchResult::new(usage, "oauth")never got a cost snapshot, so the monthly limit and spend that web showed were invisible on OAuth.seven_day_opusand nothing else, so an account with no Opus pool rendered an empty slot where Sonnet belonged. OAuth already had the Opus-then-Sonnet fallback.What changed
rust/src/providers/claude/usage_api.rsis new and owns the wire shape:ClaudeUsageResponse,ClaudeUsageWindow,ClaudeExtraUsage, plusbuild_snapshotandextra_usage_cost. Both fetchers deserialize into it and build through it.The map-based deserializer that web needed — Anthropic ships overlapping keys like
seven_day_designandseven_day_omelettein one payload, which serde aliases reject as a duplicate field — now covers OAuth too, with both casings accepted on every key.build_snapshottakes the window converter as a closure, which preserves the one difference that is real: OAuth'sto_rate_windowreturnsOptionand skips windows with no utilization; web's always emits. Both keep the behavior they had.Behavior change beyond the three symptoms
An OAuth window is renamed. OAuth aliased
seven_day_omeletteonto routines, so it rendered as "Daily Routines". It maps to the promotional window now and reads "Weekly promo", as it already did on web. The web name is the correct one; this is a visible label change for anyone whose payload carries that key.Web's cost path is unchanged. An earlier revision of this PR added a second extra-usage fallback at the cost site; CodeRabbit caught that the fetch site already falls back to the embedded payload when the dedicated overage endpoint fails, so the second one could only fire when that endpoint succeeded and reported
is_enabled: false— turning a deliberate "overage is off" into a cost read from the embedded copy. Removed in 261538c.Open item before release
used_creditsis treated as money spent. Web has always done this and it is unchanged here — but this PR propagates that assumption to OAuth for the first time. Given #208 just fixed exactly this inversion in Codex and OpenCode Go, and that audit turned up five further findings, the number is worth confirming against a live OAuth account in the running app. It is unreachable from tests and mocks.Verification
cargo test --manifest-path rust/Cargo.toml— 807 passed (805 baseline, +2 new)cargo test --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml— 474 passedcargo clippy --all-targets -- -D warnings— clean on both manifestscargo fmt --check— cleanTwo new regression tests in
usage_api.rscover all three symptoms: an OAuth-shaped payload keeps Design and converts extra-usage cents to dollars with its limit; a web-shaped payload with no Opus pool puts Sonnet in the model-specific slot. Existing test counts per file are unchanged — nothing was deleted to get here.🤖 Generated with Claude Code
Summary by CodeRabbit