fix(server): Claude usage meter follows live rate-limit updates below the warning threshold#28869
Problem
Claude's usage meter falls behind Claude Code's own /usage during active work. The Claude CLI sends a rate_limit_event every time a usage window's whole-percent value changes. Below the warning threshold, though, the per-window readings are only in an untyped unifiedWindows field. The top-level utilization is set only after a warning threshold has been passed. claudeRateLimitEventToUpdate read only the top-level fields, so under normal use it dropped every live frame. The meter then only moved when the 5-minute background provider check ran.
To reproduce: run Claude turns and watch the Session meter while the account is below its warning threshold. The CLI streams new readings, but T3's meter stays at the last probe value.
Change
claudeRateLimitEventToUpdate now decodes unifiedWindows with a Schema and maps each entry onto the rows the probe already creates:
five_hour→ Sessionseven_day→ Weeklyseven_day_overage_included→ the row for the model the probe named (still dropped while unnamed)
The top-level bucket is still read for older CLIs that don't send unifiedWindows. When both sources name the same window, the unifiedWindows value wins and no row is duplicated. Contracts, settings, polling and the adapter are unchanged.
Scope and approval
Fixes pingdotgg/t3code#28852
This is a focused fix for a triaged bug in the existing Claude usage-limit stream mapping. It adds no option, contract field or persisted state.
Verification
vp test run apps/server/src/provider/claudeUsageLimits.test.ts: 10/10 pass.maps every window in the unifiedWindows the CLI streams below the warning thresholdfails on main (expected undefined to deeply equal { windows: [ …(2) ] }) and passes here.keeps one row per window when a warning frame also carries unifiedWindowscovers the case where both sources name the same window (no duplicate row) and the overage-included bucket arriving throughunifiedWindows.
vp run check:changed: format, lint, knip, typecheck (3 packages) and tests pass.
Runtime transcript (server-only fix, no UI change). I ran a real T3 server (t3 serve, fresh database each side) whose Claude provider runs the real Claude CLI 2.1.304, signed in as a synthetic subscriber. The CLI talks to a local stub Anthropic API. The stub returns anthropic-ratelimit-unified-* headers with 5-hour utilization rising 3 points per request and 7-day fixed at 31%, and it answers /api/oauth/usage, so the initial usage probe succeeds. Three turns were driven with t3 thread start --wait. After each step I read Claude's published usageLimits from the provider status cache.
| Session meter, step | Before (main claudeUsageLimits.ts) | After (this branch) |
|---|---|---|
| initial probe | 12% | 12% |
| after turn 1 | 12% | 15% |
| after turn 2 | 12% | 21% |
| after turn 3 | 12% | 27% |
unifiedWindows.five_hour frames T3 received | 0.12 … 0.27 | 0.12 … 0.27 |
Before transcript (main's claudeUsageLimits.ts, blob d0acf43bb4)
| 1 | initial probe: checkedAt 2026-11-01T20:09:37.831Z unavailable None windows [('five_hour', 12), ('seven_day', 31)] |
| 2 | after turn 1: checkedAt 2026-11-01T20:09:37.831Z unavailable None windows [('five_hour', 12), ('seven_day', 31)] |
| 3 | after turn 2: checkedAt 2026-11-01T20:09:37.831Z unavailable None windows [('five_hour', 12), ('seven_day', 31)] |
| 4 | after turn 3: checkedAt 2026-11-01T20:09:37.831Z unavailable None windows [('five_hour', 12), ('seven_day', 31)] |
| 5 | stub 5h utilization sent on each /v1/messages response: 0.12 0.15 0.18 0.21 0.24 0.27 |
| 6 | rate_limit_event unifiedWindows.five_hour received by T3: 0.12 0.15 0.18 0.21 0.24 0.27 |
After transcript (this branch, blob 68135fc2e9)
| 1 | initial probe: checkedAt 2026-11-01T20:08:09.058Z unavailable None windows [('five_hour', 12), ('seven_day', 31)] |
| 2 | after turn 1: checkedAt 2026-11-01T20:08:17.017Z unavailable None windows [('five_hour', 15), ('seven_day', 31)] |
| 3 | after turn 2: checkedAt 2026-11-01T20:08:24.352Z unavailable None windows [('five_hour', 21), ('seven_day', 31)] |
| 4 | after turn 3: checkedAt 2026-11-01T20:08:31.651Z unavailable None windows [('five_hour', 27), ('seven_day', 31)] |
| 5 | stub 5h utilization sent on each /v1/messages response: 0.12 0.15 0.18 0.21 0.24 0.27 |
| 6 | rate_limit_event unifiedWindows.five_hour received by T3: 0.12 0.15 0.18 0.21 0.24 0.27 |
A frame the CLI sent below the warning threshold, from T3's provider event log
| 1 | {"status":"allowed","resetsAt":1793576909,"rateLimitType":"five_hour","isUsingOverage":false,"unifiedWindows":{"five_hour":{"utilization":0.18,"resetsAt":1793576909},"seven_day":{"utilization":0.31,"resetsAt":1793994509}}} |
The stub, CLI wrapper, TLS proxy and driver scripts are scratch and are not committed. They could not be uploaded because ghdrop is not available in this environment. All data in them is synthetic.
Not verified: a real Claude subscription account (none is available here; the stub reproduces the CLI's documented frames and headers), and a visual check in a client. The fix changes only server-side mapping into the existing provider snapshot, so web, desktop and mobile render it through their existing meter paths.
Model and harness: Claude Opus 5.5 (lead, red test, implementation, review B) and GPT-6.1 Sol (review A) in T3 Code.
🤖 Generated with Claude Code
Checks
Added 3 commits
Approvability
Verdict: Approved at a2149de
Macroscope's review found this PR approvable — Claude’s usage meter now consumes live per-window updates below the warning threshold while preserving older CLI compatibility. The change affects usage reporting, not rate-limit enforcement or billing.
No code changes detected at 3610683. Prior analysis still applies.
You can add or adjust custom eligibility rules. Learn more.
Important
Review skipped
Review was skipped as selected files did not have any reviewable changes.
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-28852
- File: .coderabbit.config.ts
- Commit: 3610683
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
a5ee467d-d88d-4c4e-ab1c-7083a0d354e8
External image Commits
-
Reviewing files that changed from the base of the PR and between 35bb7bbabdefb23ef4235a6f42b5bf8de62c6f55 and 36106834ddfb58f2cbe132512f4bf366413c81df.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
- Trigger review
No actionable comments were generated in the recent review.
Walkthrough
-
Claude rate-limit event parsing now converts recognized
unifiedWindowsentries into usage updates. It also accepts legacy top-level events when their window IDs are not already present. Tests cover standard and warning events.Changes: Claude usage windows
Layer / File(s) Summary Decode and collect usage windows
apps/server/src/provider/claudeUsageLimits.ts,apps/server/src/provider/claudeUsageLimits.test.tsThe parser validates streamed window fields, maps recognized windows to updates, and avoids duplicate IDs when it processes legacy top-level events. Tests cover percentage conversion, reset timestamps, and named overage-included rows. Estimated code review effort: 2 (Simple) | ~12 minutes
Change: Bug fix · Severity of issue fixed: Low
Possibly related issues
- Issue 27750 concerns streamed
five_hourevents. This change mapsfive_hourto a Session window. - Issue 24114 concerns
unifiedWindowsdata during setup-token sessions. This change adds decoding forunifiedWindows; unsupported-probe handling is not included. - Issue 27772 identifies rate-limit events as a source of active-session readings. This change maps
unifiedWindowsfrom those events; probe back-off is not included.
Suggested reviewers:
maria-rcks - Issue 27750 concerns streamed
Merge Risk: External image Minimal · previous review 00327
-
The usage-window update has no identified merge-blocking risk.
Pre-merge checks: Passed 4
-
Check name Status Explanation Title check Passed The title is concise, conventional, and clearly identifies the main change: fixing live Claude usage-meter updates below the warning threshold. Description check Passed The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the failure mode, implementation, linked issue, focused tests, runtime validation, lim… Linked Issues check Passed Issue [ #28852] requires the T3 usage meter to stay current during active Claude Code work.claudeRateLimitEventToUpdatenow decodes streamedunifiedWindowsvalues and mapsfive_hourand `seven_d…Out of Scope Changes check Passed The diff changes only the Claude usage-limit event mapper and its tests. The changes directly support issue [ #28852] by processing live rate-limit frames. No unrelated contracts, settings, polling, ad…Full details: Description check
-
Explanation
The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the failure mode, implementation, linked issue, focused tests, runtime validation, limitations, and agent details.
Full details: Linked Issues check
-
Explanation
Issue [
#28852] requires the T3 usage meter to stay current during active Claude Code work.claudeRateLimitEventToUpdatenow decodes streamedunifiedWindowsvalues and mapsfive_hourandseven_dayto the existing Session and Weekly rows. It also maps the named overage row and preserves the older top-level event path. The added tests cover below-warning updates and duplicate prevention when both sources provide a window.
Full details: Out of Scope Changes check
-
Explanation
The diff changes only the Claude usage-limit event mapper and its tests. The changes directly support issue [
#28852] by processing live rate-limit frames. No unrelated contracts, settings, polling, adapter, UI, or persisted-state changes are present.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info
-
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-28852
- File: .coderabbit.config.ts
- Commit: 00327aa
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
e7d47009-baad-4526-9fdb-968da8f20087
External image Commits
-
Reviewing files that changed from the base of the PR and between d897d3f3f00f2cb534c813e637154d39c8d1cff6 and 00327aaada0003870e0cefc6e7dcb67c6dc5f79e.
External image Files selected for processing: 2
-
apps/server/src/provider/claudeUsageLimits.test.tsapps/server/src/provider/claudeUsageLimits.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
External image Recent review info
-
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-28852
- File: .coderabbit.config.ts
- Commit: 00327aa
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
e7d47009-baad-4526-9fdb-968da8f20087
External image Commits
-
Reviewing files that changed from the base of the PR and between d897d3f3f00f2cb534c813e637154d39c8d1cff6 and 00327aaada0003870e0cefc6e7dcb67c6dc5f79e.
External image Files selected for processing: 2
-
apps/server/src/provider/claudeUsageLimits.test.tsapps/server/src/provider/claudeUsageLimits.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
-
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 5.0 KiB | 5.0 KiB | 0 B (0.0%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 21.5 KiB | 21.5 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Codex | Live turn messages | 1 | 1 | 0 (0.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.0 KiB | 0 B (0.0%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.9 KiB | 21.9 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Claude | Live turn messages | 1 | 1 | 0 (0.0%) | 8 | ✅ |
Baseline: c90b678 · PR result: 3610683 · Source CI: success
Scenario and decoded snapshot size
10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
- Codex decoded thread snapshot: 108.5 KiB
- Claude decoded thread snapshot: 108.8 KiB
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.
Added 1 commit
- Merge origin/main into fix/issue-2885235bb7bb
The automated pipeline stopped on this pull request and will not merge it:
someone else pushed to fix/issue-28852 (head is now 00327aaada); the pipeline does not build on commits it did not review
It stays open for a person to finish or close.
Added 1 commit
- Merge origin/main into fix/issue-288523610683
The automated pipeline stopped on this pull request and will not merge it:
the proof has no capture anyone can open:
- Permanent blocker: first-party Claude Pro OAuth is unavailable, preventing the required live usage comparison on both commits.
- Session and Weekly meter updates below the warning threshold remain unverified in the real app.
- No public capture URLs exist because no valid runtime capture could be produced; no upload was attempted.
- Both verdict flags are false because neither outcome was observed, not because either commit was shown to behave incorrectly.
It stays open for a person to finish or close.