fix: auto-decay backoffLevel when rate limit window has passed

High backoffLevel (up to 15) persisted permanently in the DB after a burst of 429s. The account health score dropped to zero (100 - 15*10 = -50), causing the account selector to never pick the account again. Only a successful request could reset backoffLevel via clearAccountError, but the account was never selected — creating a deadlock.

Now, during account selection, any non-terminal connection whose rateLimitedUntil has passed gets its backoffLevel reset to 0 and testStatus restored to active. The DB update is fire-and-forget to avoid blocking the hot path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Brendan DeBeasi
2026-03-26 20:00:32 -07:00
parent 6acd36e374
commit 7df0c1607e

View File

@@ -368,6 +368,29 @@ export async function getProviderCredentials(
return null;
}
// Auto-decay backoffLevel for accounts whose rateLimitedUntil has passed.
// Without this, high backoffLevel permanently deprioritizes accounts even
// after the rate limit window expires, creating a deadlock where the account
// needs a successful request to reset but never gets selected.
for (const c of connections) {
if (
c.backoffLevel > 0 &&
!isTerminalConnectionStatus(c) &&
!isAccountUnavailable(c.rateLimitedUntil)
) {
c.backoffLevel = 0;
updateProviderConnection(c.id, {
backoffLevel: 0,
testStatus: "active",
lastError: null,
lastErrorAt: null,
lastErrorType: null,
lastErrorSource: null,
errorCode: null,
}).catch(() => {});
}
}
// Filter out unavailable accounts and excluded connection
const availableConnections = connections.filter((c) => {
if (excludeConnectionId && c.id === excludeConnectionId) return false;