[BUG] Usage banner % reads 1 point lower than /usage (floor-vs-round plane mismatch); Math.floor(x*100) IEEE-754 artifact drops a 2nd point at 29/57/58%
Summary
The session-start usage banner (You've used N% of your …) frequently shows 1 percentage point lower than the /usage dialog and the claude.ai usage page for the same instant — and at exactly 29%, 57%, and 58% it drops one more point due to an IEEE-754 artifact in the display math. Worst case the banner reads 2 points below /usage (e.g. banner 28%, /usage 30%).
Two independent causes stack on the same displayed number. Verified against Claude Code 2.1.206 on macOS (Max 20x plan).
Cause 1 — floor-vs-round mismatch between the two data planes (structural, ~half the time)
The banner and the /usage dialog read different sources with different server-side rounding:
- The banner renders from the unified rate-limit header cache. The
anthropic-ratelimit-unified-*-utilizationheaders arrive as a whole-percent fraction that is floored server-side (e.g.0.87). - The
/usagedialog callsGET /api/oauth/usage, whoseutilizationarrives rounded to nearest server-side (integer percent).
So whenever true utilization has fractional part ≥ .5 — about half the time — the two surfaces disagree by exactly 1 point (banner 96%, /usage 97%).
Observed live, both windows sampled at the same instant: headers said 0.87 / 0.37 while the endpoint said 88 / 38; minutes later headers 0.88 == endpoint 88 (which rules out the endpoint ceiling-ing).
Cause 2 — Math.floor(utilization * 100) drops an extra point at 0.29 / 0.57 / 0.58 (deterministic)
The banner computes Math.floor(utilization * 100) on the header fraction. For exactly three of the hundred possible floored values, the double-precision product lands just below the integer, so Math.floor removes one more point:
$ node -e 'for (const x of [0.29, 0.57, 0.58]) console.log(x, x*100, Math.floor(x*100))'
0.29 28.999999999999996 28
0.57 56.99999999999999 56
0.58 57.99999999999999 57
So when the server has already floored utilization to 0.29, the banner shows 28%. Combined with Cause 1: true utilization 29.5% → server floors headers to 0.29 → banner shows 28% while /usage shows 30%.
Steps to reproduce
- Compare the session-start banner percent against
/usageat the same moment: they disagree by 1 point whenever the true fraction is ≥ .5 (roughly half of all samples). - Deterministic case: when any API response carries
anthropic-ratelimit-unified-5h-utilization: 0.29(or0.57/0.58), the banner renders 28% (56% / 57%). - The
Math.floor(x*100)artifact reproduces standalone with the node one-liner above.
Expected
All Claude Code surfaces (session-start banner, /usage dialog) and claude.ai show the same integer percent for the same instant.
Suggested fix
- Client: use
Math.round(utilization * 100)on the banner path. The header fraction is already quantized to whole percents server-side, so rounding is exact and this fully eliminates Cause 2. (Math.floor(x*100 + 0.5)equivalently.) - Consistency (likely server-side): pick one rounding mode for both planes — the floored
anthropic-ratelimit-unified-*-utilizationheaders vs the round-to-nearestGET /api/oauth/usageutilization— so the banner and/usagecan't structurally disagree by 1 point half the time.
Related (same banner-floors-things family, distinct bugs)
- #71925 — banner floors the reset time ("resets in 1h" at 1h 20m) — closed
- #67048 — rate-limit reset countdown truncates to floor(hours)
Environment
- Claude Code 2.1.206
- macOS (darwin 25.x, Apple Silicon)
- Plan: Max 20x (claude.ai OAuth)