fix(sse): clear quota_exhausted cooldown when real window recovers (#10534)

* fix(sse): clear quota_exhausted cooldown when real window recovers

The claude-token-fallback combo was not auto-returning to Sonnet/Opus
after a subscription 429 recovered. maybeClearRecoveredQuotaState()
was honoring the synthetic 1h cooldown (SUBSCRIPTION_QUOTA_COOLDOWN_MS,
persisted when no upstream reset was parseable) instead of the REAL
per-window resetAt returned by the scheduled quota poller, so the
connection stayed locked long past the actual quota reset.

Add windowStillExhaustedAfterRealReset() and use it to decide recovery
per-quota-window: a quota_exhausted connection now clears as soon as no
governing window is still exhausted with a future-or-unknown real
reset, instead of waiting out the synthetic cooldown. Falls back to the
previous synthetic-cooldown guard when the fetch has no quota object at
all (degraded/failed shape) so existing behavior is unchanged there.

Preserves the existing kimi-coding partial-refresh semantics: an
exhausted window with no parseable resetAt still blocks recovery.

* fix(sse): preserve Claude extra-usage block from general quota recovery

maybeClearRecoveredQuotaState()'s new per-window recovery check (added in
this branch) only inspected usage.quotas, so a Claude connection blocked by
the extra-usage guard (lastErrorSource: "extra_usage") could be released
just because the session/weekly quota windows looked recovered, even while
extraUsage.queued was still true. Extra-usage blocking is orthogonal to
quota-window exhaustion and must only be released by
syncClaudeExtraUsageStateIfNeeded (buildClaudeExtraUsageConnectionUpdate).

Add a guard that keeps the connection locked when lastErrorSource is
"extra_usage", the blockExtraUsage policy is still enabled, and the fresh
usage snapshot still reports extraUsage.queued === true.

Add an integration test walking the real
fetchLiveProviderLimitsWithOptions -> syncClaudeExtraUsageStateIfNeeded ->
maybeClearRecoveredQuotaState call chain with recovered quota windows but
extraUsage.queued=true, asserting the connection stays unavailable with
lastErrorSource still "extra_usage".

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
SnCr90
2026-08-18 16:51:47 +03:00
committed by GitHub
parent 9a433775e7
commit 276b3dffa3
2 changed files with 249 additions and 34 deletions

View File

@@ -13,7 +13,12 @@ import {
} from "@/lib/db/providerLimits";
import { syncToCloud } from "@/lib/cloudSync";
import { setQuotaCache } from "@/domain/quotaCache";
import { buildClaudeExtraUsageConnectionUpdate } from "@/lib/providers/claudeExtraUsage";
import {
buildClaudeExtraUsageConnectionUpdate,
CLAUDE_EXTRA_USAGE_ERROR_SOURCE,
isClaudeExtraUsageBlockEnabled,
isClaudeExtraUsageQueued,
} from "@/lib/providers/claudeExtraUsage";
import { clearRecoveredProviderState } from "@/sse/services/auth";
import { getMachineId } from "@/shared/utils/machine";
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
@@ -433,18 +438,66 @@ export function hasUsableQuota(usage: JsonRecord): boolean {
return false;
}
// A window "still blocks" recovery when it governs quota and is either still
// exhausted with a real reset that hasn't passed yet, or exhausted with no
// parseable real reset at all (unknown-reset windows stay locked, matching
// the pre-existing kimi-coding partial-refresh semantics).
function windowStillExhaustedAfterRealReset(value: unknown, nowMs: number): boolean {
if (!isRecord(value)) return false;
if (value.unlimited === true) return false;
const remaining =
typeof value.remaining === "number"
? value.remaining
: typeof value.remainingPercentage === "number"
? value.remainingPercentage
: null;
if (remaining !== null && remaining > 0) return false;
if (value.resetAt == null) return true;
const resetMs = Date.parse(String(value.resetAt));
if (Number.isNaN(resetMs)) return true;
return resetMs > nowMs;
}
export async function maybeClearRecoveredQuotaState(
connection: ProviderConnectionLike,
usage: JsonRecord
): Promise<ProviderConnectionLike> {
if (!hasUsableQuota(usage)) return connection;
if (isTerminalStatusForQuotaRecovery(connection.testStatus)) return connection;
if (
connection.lastErrorType === "quota_exhausted" &&
connection.rateLimitedUntil &&
new Date(connection.rateLimitedUntil).getTime() > Date.now()
) {
return connection;
if (connection.lastErrorType === "quota_exhausted") {
if (
connection.lastErrorSource === CLAUDE_EXTRA_USAGE_ERROR_SOURCE &&
isClaudeExtraUsageBlockEnabled(connection.provider, connection.providerSpecificData) &&
isClaudeExtraUsageQueued(usage)
) {
// Claude's pay-as-you-go extra-usage block is orthogonal to the
// session/weekly quota windows checked below: the upstream can report a
// fully recovered quota window while extraUsage.queued is still true.
// Only syncClaudeExtraUsageStateIfNeeded (buildClaudeExtraUsageConnectionUpdate)
// owns clearing this specific state — the general window-recovery logic
// below must not release it just because some quota window looks fresh.
return connection;
}
const quotas = usage?.quotas;
if (isRecord(quotas)) {
// Honor the REAL per-window resetAt from the freshly fetched quota
// instead of the synthetic cooldown persisted at failure time (e.g.
// Claude's flat 1h SUBSCRIPTION_QUOTA_COOLDOWN_MS when no upstream
// reset was parseable). Only stay locked if some window that governs
// this connection's quota is still demonstrably exhausted.
const anyStillBlocking = Object.values(quotas).some((value) =>
windowStillExhaustedAfterRealReset(value, Date.now())
);
if (anyStillBlocking) return connection;
} else if (
connection.rateLimitedUntil &&
new Date(connection.rateLimitedUntil).getTime() > Date.now()
) {
// No quota object at all (degraded/failed fetch shape) — fall back to
// the previous synthetic-cooldown guard.
return connection;
}
}
const hasTransientState =