Skip to content

Claude usage limits never populate: rate_limit_event numbers live under unifiedWindows, and get_usage returns rate_limits: null #30310

Open

Description

@R2-Claw2

What happens

Every Claude provider instance reports
usageLimits: { windows: [], unavailable: { reason: "probeFailed" } } in
server.getConfig, and checkedAt never moves after real turns. Codex
instances on the same server report their session and weekly windows fine.

Seen on T3 0.1.6 and on v0.1.7-nightly.20261103.4119 (both code paths are
identical), with Claude Code 2.1.306 on a Pro subscription, macOS arm64.

Cause

Two separate paths in apps/server/src/provider/claudeUsageLimits.ts:

  1. Per-turn rate_limit_event is dropped. claudeRateLimitEventToUpdate
    returns early unless info.utilization is a number:

    if (!type || typeof info.utilization !== "number") {
      return;
    }

    Claude Code 2.1.306 sends no top-level utilization. The numbers are in
    unifiedWindows, as 0..1 fractions:

    {
      "type": "rate_limit_event",
      "rate_limit_info": {
        "status": "allowed",
        "resetsAt": 1793736600,
        "rateLimitType": "five_hour",
        "overageStatus": "rejected",
        "overageDisabledReason": "org_level_disabled",
        "isUsingOverage": false,
        "unifiedWindows": {
          "five_hour": { "utilization": 0.49, "resetsAt": 1793736600 },
          "seven_day": { "utilization": 0.11, "resetsAt": 1793768400 }
        }
      }
    }

    So every event maps to undefined and onUsageLimits is never called.

  2. The get_usage probe gets rate_limits: null. The capability probe
    sends the get_usage control request; the CLI answers
    rate_limits_available: true with rate_limits: null, which
    claudeUsageResponseToLimits turns into probeFailed.

Suggested fix

In claudeRateLimitEventToUpdate, when info.unifiedWindows is present, map
each known key (five_hour, seven_day, and the model-scoped ones) with
utilization * 100 and its own resetsAt, and keep the existing flat
utilization path for older CLIs. That alone makes the windows appear after the
first turn, even while the get_usage probe still returns null.

Repro

  1. Register a Claude provider instance logged in with a subscription.
  2. Run one turn.
  3. server.getConfig → providers[instance].usageLimits is still
    probeFailed with no windows, while the provider event log for the thread
    contains the rate_limit_event above.

Activity

  1. joh-klein commented on Nov 4, 2026

    @joh-klein

    Another data point, with a cause that PR #30999 alone will likely not fix.

    Setup

    Why the probe returns rate_limits: null here

    With that flag set, get_usage answers in about 7 ms with rate_limits_available: true, rate_limits: null. The CLI debug log shows fetchUtilization: GET /api/oauth/usage (attempt 1), then nothing. A direct authenticated GET to /api/oauth/usage returns 200 with full data. So for org-managed accounts the probe will never succeed, not just "before the first turn". The per-turn rate_limit_event is the only source, because it comes from inference response headers, which count as essential traffic.

    The events are already parsed, but the window still doesn't show

    On this CLI the events still have the flat fields, so the current code already maps them:

    "rate_limit_info":{"status":"allowed_warning","resetsAt":1793851200,"rateLimitType":"seven_day","utilization":0.92,
     "overageStatus":"allowed","isUsingOverage":false,"surpassedThreshold":0.75,
     "unifiedWindows":{"five_hour":{"utilization":0.05,"resetsAt":1793830800,"status":"allowed"},
                       "seven_day":{"utilization":0.92,"resetsAt":1793851200,"status":"allowed_warning","surpassedThreshold":0.75}},
     "overageEnabled":true}

    The provider cache (~/.t3/caches/claudeAgent.json) then holds:

    "usageLimits": {
      "checkedAt": "2026-11-04T20:55:47.881Z",
      "windows": [{ "id": "seven_day", "kind": "weekly", "usedPercent": 93, "resetsAt": "2026-11-05T04:00:00.000Z" }],
      "unavailable": { "reason": "probeFailed" }
    }

    The Limits view still shows Claude as unavailable. Two gaps remain:

    1. applyUsageLimitsUpdate keeps previous.unavailable (probeFailed) after merging streamed windows. It looks like the UI treats unavailable as "no data" and hides the windows it has. I haven't traced the renderer, so this is inferred from the stored state plus what the UI shows.
    2. Only the event's rateLimitType window is taken. five_hour in unifiedWindows is ignored. fix(server): Claude limits update from rate_limit_event unifiedWindows #30999 addresses this part.

    Suggestion

  2. joh-klein commented on Nov 4, 2026

    @joh-klein

    Follow-up: fix in #31135, tested in a real environment with the org-managed flag. With #30999 applied as well, Usage → Limits shows Session and Weekly, and both survive a manual refresh (a failed probe). Without #31135, the released app has the Weekly window in its cache, but the UI hides it.

    Note: #31135 changes behavior from #15116, which made a failed probe drop streamed windows. The PR keeps the probeFailed marker but keeps and shows the windows, because for these accounts a full probe never succeeds. The trade-off is explained in the PR. It needs a maintainer call.

  3. Wizhill05 commented on Nov 7, 2026

    @Wizhill05

    Confirming this still reproduces on the latest nightly, with a newer Claude Code than the report.

    Setup

    • T3 Code 0.1.8-nightly.20261107.4327 (also seen on 0.1.5-nightly.20261029.3867), Linux x64, background service
    • Claude Code 2.1.319 (also seen on 2.1.314), Pro subscription, claude.ai OAuth
    • Claude shows no limits in T3. Antigravity limits do appear.

    Findings

    1. rate_limit_event is still dropped. claudeRateLimitEventToUpdate in 0.1.8 is unchanged and returns early unless info.utilization is a top-level number. Events from 2.1.319 carry the numbers only under unifiedWindows:
      {"status":"allowed","resetsAt":1794091200,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"org_level_disabled","isUsingOverage":false,"unifiedWindows":{"five_hour":{"utilization":0.05,"resetsAt":1794091200,"status":"allowed"},"seven_day":{"utilization":0,"resetsAt":1794139200,"status":"allowed"}}}
    2. get_usage still returns rate_limits: null. I ran the capability probe directly with @anthropic-ai/claude-agent-sdk against the 2.1.319 binary, with and without CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, polling for ~10s. Every response was rate_limits_available: true, rate_limits: null, so the flag is not the cause on this setup.
    3. The data is available to the account. GET /api/oauth/usage with the same token returns session 2% and weekly 0%, and T3's own status checks (checkClaudeProviderStatus) succeed with no auth errors.

    So a user with a plain Pro login and no managed settings hits both paths. This matches the first fix proposed in the issue (map unifiedWindows in the event handler). #30999 was closed unmerged, so as far as I can tell nothing on main handles that case yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions