Repository navigation
Conversation
The Claude usage endpoint can answer a 429 with `Retry-After: 0`. `State::limited` used the header verbatim, so the cooldown expired in the same second and every TUI refresh re-requested while the limit was still in force, showing "HTTP 429; retry in 0s". `Retry-After` now only lengthens the progressive backoff (60s doubling, capped at one hour); a larger server value is still honored. The same-second auth-rejection test used a `Retry-After: 0` 429 to get a second request into the same clock second. It now builds that equal-second disk state directly, keeping its original assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Claude usage endpoint (
/api/oauth/usage) can answer HTTP 429 withRetry-After: 0.State::limitedused that value verbatim, so the cooldown expired in the same second it was set: the state file gotuntil = nowand the cooldown file gotexpires_at = now. Every TUI refresh (auto-refresh,r, click) then sent another request while the limit was still in force, and the Usage tab kept showingHTTP 429; retry in 0s.Follow-up to #1557 and #1188, which added the cooldown machinery. This PR closes the case where the server's own hint disables it.
What changed
crates/tokscale-cli/src/commands/usage/claude_state.rs:Retry-Aftercan now only lengthen the progressive backoff. It no longer replaces it:wait = min(max(retry_after, 60s × 2^(failures-1)), 3600s).Retry-After: 0, an HTTP-date already in the past, or any value below the backoff → 60s, 120s, 240s, … up to 1h.Retry-After: 300) is still honored as-is.fetch_policy, so this one change covers all of them.Testing
State::limited: zero, a value below the backoff, a value above it, and repeated zeros capping at 3600s.fetch_report_atagainst a local HTTP server that always answers429+ the givenRetry-After, asserting on the number of requests that actually reached the server:policy_zero_retry_after_blocks_requests_until_backoff_elapses: refreshes at +0/+30/+60/+120/+180s send requests only at +0, +60, +180, with waits of 60s, 120s, 240s. No diagnostic containsretry in 0s.policy_zero_retry_after_cooldown_survives_restart: drops both in-memory maps to simulate a new tokscale process, and confirms no request goes out at +30s.policy_past_http_date_retry_after_backs_offpolicy_same_second_auth_rejection_survives_failed_state_writeused aRetry-After: 0429 to land a second request in the same clock second, which is exactly the behavior this PR removes. It now builds that equal-second disk state directly (writesobserved_at = 1060into the state file). All original assertions are kept. Removing the(observed_at, revision, sample.is_none())tie-break instate::loadstill makes it fail withrejected quota resurrected from equal-time disk state.cargo test -p tokscale-cli,cargo fmt --all -- --check, andcargo clippy -p tokscale-cli --all-targets --no-deps -- -D warningsall pass.Verification
Built the binary with
USAGE_URLtemporarily pointed at a local server that always returns429+Retry-After: 0. Rantokscale usage --lightthree times, ~7s apart, each as a separate process with an isolatedTOKSCALE_CONFIG_DIR:mainHTTP 429; retry in 0s×3HTTP 429; retry in 60s→rate-limited; retry in 53s→rate-limited; retry in 46sThe real endpoint's 429 can't be reproduced on demand, so this was verified against the local stand-in only.
Notes
failuresresets only on a successful fetch, not with time. If seven or more 429s happen in a row with no success in between, a later short serverRetry-After(e.g. 30s) is raised to the 1h cap. This is conservative and matches how header-less 429s already escalate. A time-based decay could be a separate follow-up if wanted.🤖 Generated with Claude Code
Summary by cubic
Fixes the Claude usage endpoint cooldown so a 429 with
Retry-After: 0no longer disables backoff.State::limitedhonored the header verbatim, so the cooldown expired in the same second and every TUI refresh re-requested while the limit was still in force, showing “HTTP 429; retry in 0s”.Retry-Afternow only lengthens the progressive backoff (60s doubling, capped at one hour); a larger server value is still honored as-is. The same-second auth-rejection test is updated to build the equal-second disk state directly instead of relying on aRetry-After: 0429.Written for commit 6a9868d. Summary will update on new commits.