Skip to content

fix: detect stale usage data after account rate-limit/quota reset#272

Open
XavLimSG wants to merge 1 commit intoSoju06:mainfrom
XavLimSG:fix/stale-usage-after-reset
Open

fix: detect stale usage data after account rate-limit/quota reset#272
XavLimSG wants to merge 1 commit intoSoju06:mainfrom
XavLimSG:fix/stale-usage-after-reset

Conversation

@XavLimSG
Copy link
Copy Markdown

Summary

Fixes an issue where the load balancer doesn't update when user account usage windows reset (daily/weekly limit rollover). Accounts could remain blocked or deprioritised indefinitely after their limits refresh upstream.

Root causes fixed:

  • _latest_usage_is_fresh() ignored reset boundaries — the freshness check only compared record age against the refresh interval, so stale 100% usage data persisted even after reset_at had passed. Now returns false when the window has rolled over, forcing an immediate re-fetch.
  • _state_from_account() passed stale exhausted data into the balancer — when the last recorded sample showed 100% usage but the reset_at was already in the past, the account was still blocked or sorted last. Now zeros out stale percentages when the window has rolled over.
  • select_account() didn't fully clear usage on status recoveryRATE_LIMITED recovery didn't reset used_percent, and QUOTA_EXCEEDED recovery didn't reset secondary_used_percent, so recovered accounts were deprioritised in usage-weighted selection.
  • Added _account_needs_post_reset_refresh() — forces a usage refresh when an account's persisted reset_at has expired but the primary usage entry still looks fresh (covers secondary-only resets).

Files changed

File Change
app/modules/usage/updater.py _latest_usage_is_fresh() now checks reset_at; new _account_needs_post_reset_refresh() helper; refresh_accounts() uses it
app/modules/proxy/load_balancer.py _state_from_account() zeros stale 100% data when reset_at is past
app/core/balancer/logic.py select_account() resets used_percent and secondary_used_percent on status clear
tests/unit/test_usage_updater.py Tests for freshness check with reset boundary + account refresh helper
tests/unit/test_load_balancer.py Tests for selection recovery with full percent reset

Test plan

  • All 677 existing unit tests pass with no regressions
  • New tests cover: freshness check with expired reset_at, account refresh detection, selection sort priority after reset
  • Manual testing with accounts that hit rate limits and wait for window rollover

🤖 Generated with Claude Code

When an account's usage window resets (e.g. daily/weekly limit rollover),
the load balancer could continue treating the account as exhausted because:

1. `_latest_usage_is_fresh()` only checked record age, not whether the
   `reset_at` timestamp had passed — stale 100% data persisted until the
   next refresh interval even though the upstream limit had already reset.

2. `_state_from_account()` passed stale 100% usage into the balancer
   and sort keys even when `reset_at` was in the past, causing the
   account to be blocked or deprioritised.

3. `select_account()` cleared the blocked status when `reset_at` expired
   but did not reset `used_percent` (RATE_LIMITED) or
   `secondary_used_percent` (QUOTA_EXCEEDED), so the account remained
   last in usage-weighted selection.

This commit fixes all three layers:

- Freshness check now returns false when `reset_at` has passed, forcing
  an immediate re-fetch from the upstream API.
- A new `_account_needs_post_reset_refresh()` helper forces refresh even
  when primary data looks fresh but the account's persisted reset time
  has expired (covers secondary-only resets).
- `_state_from_account()` zeros out stale exhausted percentages when the
  window has rolled over.
- `select_account()` resets both `used_percent` and
  `secondary_used_percent` when clearing expired statuses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Soju06
Copy link
Copy Markdown
Owner

Soju06 commented Mar 30, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ad7f43c5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +191 to +195
# Still force a refresh when the account is blocked with an
# expired reset — the fresh primary entry does not capture
# that the upstream window has turned over.
if not _account_needs_post_reset_refresh(account, now):
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bypass stale gate when forcing post-reset usage refresh

This branch intends to force a refresh for blocked accounts whose reset_at has already expired, but it still falls through to _refresh_account_if_stale(), which re-checks primary freshness and can return early without fetching. In the secondary-window rollover scenario (account remains QUOTA_EXCEEDED/RATE_LIMITED, primary row still fresh), the new _account_needs_post_reset_refresh path does not actually trigger an upstream refresh, so recovery is delayed until the normal primary interval expires.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants