[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%

Open 💬 0 comments Opened Jul 10, 2026 by willmcginnis

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-*-utilization headers arrive as a whole-percent fraction that is floored server-side (e.g. 0.87).
  • The /usage dialog calls GET /api/oauth/usage, whose utilization arrives 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

  1. Compare the session-start banner percent against /usage at the same moment: they disagree by 1 point whenever the true fraction is ≥ .5 (roughly half of all samples).
  2. Deterministic case: when any API response carries anthropic-ratelimit-unified-5h-utilization: 0.29 (or 0.57 / 0.58), the banner renders 28% (56% / 57%).
  3. 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-*-utilization headers vs the round-to-nearest GET /api/oauth/usage utilization — so the banner and /usage can'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)

View original on GitHub ↗