fix: stop rescaling Claude utilization below 1% to 100% - #274
Conversation
`normalize_utilization` treated any `utilization` value in (0, 1] as a fraction and multiplied it by 100. The Claude usage API already reports these values in percent units, so a session at 1% was rendered as a fully consumed quota with a red "used up" state, while the Claude settings page showed 1%. The sibling `limits[].percent` field has always been consumed as-is by `scoped_weekly::weekly_all_window`, and issue payloads carry the same value in both fields (`seven_day.utilization: 1.0` alongside `weekly_all.percent: 1.0`), confirming the unit. Drop the rescaling in both the OAuth and web API fetchers so five-hour, seven-day, model-scoped and routines windows all report percent directly.
|
Thanks for the PR, I will review this ASAP. |
Finesssee
left a comment
There was a problem hiding this comment.
Requesting changes — this is a structural block, not a behavioral one. The fix itself is correct and validated.
The utilization reading is right: 1.0 means 1%, and the old duplicated normalize_utilization rescaling in both fetchers reported a sub-1% session as a fully consumed quota. Dropping it in favor of reading window.utilization directly, renaming the tests to keeps_sub_one_utilization_in_percent_units, and adding the one_percent_session_is_not_reported_as_full_quota regression test pins the actual failure mode.
Blocking: rust/src/providers/claude/oauth/mod.rs crosses the repo's ~1000-line file-size bar in this PR (999 → 1011). The inline #[cfg(test)] mod tests occupies lines 621–1011 — roughly 390 lines, over a third of the file — and that is exactly the growth pattern the threshold exists to catch. Before merge: extract the inline tests into rust/src/providers/claude/oauth/tests.rs (behind a mod tests; include in mod.rs), which brings mod.rs back under the bar without touching behavior.
Non-blocking follow-up: the OAuth and web fetchers still each convert provider windows into UsageWindow with duplicated percent-units semantics. That conversion should become one canonical converter shared by both paths. If the two conversions stay separate, mirror the exact 1.0 → 1% assertion in web_api.rs as well — its tests currently cover 0.23 → 0.23 and 23.0 → 23.0 but not the boundary value that caused this bug.
The scope of this request is the threshold crossing; the fix logic itself does not need to change.
Problem
A Claude session at 1% is displayed as 100% used, with the red "used up" state, while the Claude settings page reports 1% for the same window.
normalize_utilization(duplicated in the OAuth and web API fetchers) treats anyutilizationvalue in(0, 1]as a fraction and multiplies it by 100:The API already reports these values in percent units, so every window at or below 1% is inflated by 100x. Values above 1 are unaffected, which is why the bug only shows up on freshly reset windows.
Why percent is the right unit
scoped_weekly::weekly_all_windowalready consumes the siblinglimits[].percentfield as-is, without rescaling.weekly_all_limit_wins_over_stale_seven_day_utilizationtest carries"seven_day": {"utilization": 1.0}next to"kind": "weekly_all", "percent": 1.0— the same window, the same value, in both fields.limits[]path satisfied it;to_rate_windowdid not.Change
Remove
normalize_utilizationfrom both fetchers and useutilizationdirectly. This covers the five-hour session, seven-day, model-scoped (Opus/Sonnet), OAuth apps and Daily Routines windows.Tests
converts_fractional_utilization_to_percenttokeeps_sub_one_utilization_in_percent_unitsin both modules and flipped the expectation (0.23stays0.23).one_percent_session_is_not_reported_as_full_quotacovering the reported symptom directly.parses_current_snake_case_oauth_usage_response, which encoded the old behaviour (1.0->100.0,0.14->14.0).cargo test: 1207 passed, 0 failed.cargo clippy --all-targets: no new warnings.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.