# \[Bug\]: Limits hub blanks Claude accounts after a few refreshes because every refresh re-probes /api/oauth/usage and 429s are shown as a read failure · Issue \#19807 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/19807)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/19807)

---

### Before submitting

- [x] I searched existing issues and did not find a duplicate.
- [x] I included enough detail to reproduce or investigate the problem.

### Area

apps/server

### Steps to reproduce

1. Run a CLIProxyAPI hub with two or more Claude accounts.
2. In T3 Code, open **Settings → Providers → Usage providers → Add hub** and enter the hub URL and management key.
3. Open **Usage → Limits**. Both Claude accounts show their session and weekly bars.
4. Click **Refresh limits** a few times, or leave Limits and come back, within a minute or two.

Without a real hub, a minimal fake hub reproduces it deterministically: it serves `GET /v0/management/auth-files` and `POST /v0/management/api-call` like CLIProxyAPI, lists two Claude accounts, and answers each account's `https://api.anthropic.com/api/oauth/usage` probe 200 the first time and 429 for every later probe inside a burst window (what Claude does after a burst). Script (`fake-cliproxyapi-hub.mjs`, plus `measure-limits-refreshes.mjs` that calls `readAccounts` with simulated time between refreshes): https://gist.github.com/JustYannicc/92188afcc4926f76d1e9abd1066be6b3. Run `node fake-cliproxyapi-hub.mjs --port 18317`, add it as a hub with URL `http://127.0.0.1:18317` and key `test-management-key`, then refresh Limits.

### Expected behavior

Refreshing Limits doesn't send a new usage request for every account each time, and a refused (429) read leaves the account's last known bars in place rather than blanking them. If there's no earlier reading, the account says the provider is rate limiting.

### Actual behavior

Every Limits visit, refresh and hub settings change sends one `/api/oauth/usage` request per account through the hub. After the first read, Claude answers 429, and each account's bars are replaced with "The hub could not read this account's usage." They stay blank as long as the refreshes keep going, and the extra requests also count against the same Claude accounts that other clients use.

I measured this against the fake hub. It calls the server's hub usage source (`makeCliproxyApi().readAccounts`, the call `UsageLimitSources` makes on every refresh) 8 times, simulating refreshes at 0, 5, 10, 15, 20, 70, 140 and 400 seconds:

| | Usage probes sent | Answered 429 | Blank account readings (of 16) |
|---|---|---|---|
| `main` @ 339179f50a | 16 | 14 | 14 (every refresh after the first) |
| fix branch (below) | 6 | 4 | 0 |

Cause: `apps/server/src/usage/cliproxyApi.ts`, `readAccount`. It probes `https://api.anthropic.com/api/oauth/usage` through `/v0/management/api-call` every time it runs, with no per-account pacing. It then maps any failure, a 429 included, to an unavailable reading via `Effect.orElseSucceed(() => ({ ...base, usageLimits: makeUnavailableUsageLimits({ reason: "probeFailed", message: "The hub could not read this account's usage." }) }))`. `apiCall` reports the 429 as a generic `UsageLimitSourceError` ("The provider refused the hub request (HTTP 429)."), so nothing can tell a rate limit apart from any other failure. `UsageLimitSources.refresh` runs this for every account on every Limits visit and refresh (`UsagePage` auto-refreshes when Limits opens), on every `usageLimitSources` settings change, and on the provider-status interval.

A fix branch is at https://github.com/JustYannicc/t3code/tree/fix/limits-hub-probe-throttle. Each hub account is read at most once a minute, a 429 waits five minutes, and a failed read keeps the last good reading for up to fifteen minutes. Redeeming a reset forgets the account so the next read asks again. This is the same policy shape as #19474 for native Claude banked-reset reads: one read per interval, the last answer kept between reads, a longer wait after a refusal, and `forget` after a redeem. The branch doesn't depend on #19474.

### Impact

Major degradation or frequent failure

### Version or commit

`main @ 339179f50a`

### Environment

macOS (Darwin 27.2, arm64), Node 24.18.0. The hub here is a fake CLIProxyAPI that returns Claude-style 429s. Provider: Claude subscription accounts pooled in the hub.

### Logs or stack traces

```shell
# fake hub log, unchanged main, refreshes at 0s and 5s
usage probe #1 account 1 -> 200
usage probe #2 account 2 -> 200
list accounts
usage probe #3 account 1 -> 429 (total 429s: 1)
usage probe #4 account 2 -> 429 (total 429s: 2)
```

### Workaround

Don't refresh Limits more than about once every few minutes. Once the accounts are blank, wait until Claude stops answering 429 and refresh once.

> Written by Claude Opus 5.5 (Claude Code) on behalf of @JustYannicc.

