From 45755bfa18f1ed3a831341b425c17154c1020243 Mon Sep 17 00:00:00 2001 From: SvDp Date: Thu, 8 Jan 2026 21:31:50 +0530 Subject: [PATCH] fix: add optimistic reset for transient 429 rate limit errors Fixes issue #71 - 'No accounts available' error when API returns 429 The Google Cloud Code API can return 429 RESOURCE_EXHAUSTED errors even when accounts have quota available due to: - Temporary API load/throttling - Per-minute request limits (not per-day quota) - Transient backend issues This fix adds: 1. A 500ms buffer after waiting for rate limits to expire 2. Optimistic rate limit reset when all accounts appear stuck 3. Retry logic that clears rate limits and tries again The fix works in conjunction with the server-level optimistic reset that already exists, providing multiple layers of protection against false 'No accounts available' errors. Co-Authored-By: Claude --- src/cloudcode/message-handler.js | 19 +++++++++++++++---- src/cloudcode/streaming-handler.js | 19 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/cloudcode/message-handler.js b/src/cloudcode/message-handler.js index beb6745..fb1ed86 100644 --- a/src/cloudcode/message-handler.js +++ b/src/cloudcode/message-handler.js @@ -72,8 +72,19 @@ export async function sendMessage(anthropicRequest, accountManager, fallbackEnab const accountCount = accountManager.getAccountCount(); logger.warn(`[CloudCode] All ${accountCount} account(s) rate-limited. Waiting ${formatDuration(allWaitMs)}...`); await sleep(allWaitMs); + + // Add small buffer after waiting to ensure rate limits have truly expired + await sleep(500); accountManager.clearExpiredLimits(); account = accountManager.pickNext(model); + + // If still no account after waiting, try optimistic reset + // This handles cases where the API rate limit is transient + if (!account) { + logger.warn('[CloudCode] No account available after wait, attempting optimistic reset...'); + accountManager.resetAllRateLimits(); + account = accountManager.pickNext(model); + } } if (!account) { @@ -197,10 +208,10 @@ export async function sendMessage(anthropicRequest, accountManager, fallbackEnab } if (isNetworkError(error)) { - logger.warn(`[CloudCode] Network error for ${account.email}, trying next account... (${error.message})`); - await sleep(1000); // Brief pause before retry - accountManager.pickNext(model); // Advance to next account - continue; + logger.warn(`[CloudCode] Network error for ${account.email}, trying next account... (${error.message})`); + await sleep(1000); // Brief pause before retry + accountManager.pickNext(model); // Advance to next account + continue; } throw error; diff --git a/src/cloudcode/streaming-handler.js b/src/cloudcode/streaming-handler.js index 5db796f..d1d054f 100644 --- a/src/cloudcode/streaming-handler.js +++ b/src/cloudcode/streaming-handler.js @@ -71,8 +71,19 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb const accountCount = accountManager.getAccountCount(); logger.warn(`[CloudCode] All ${accountCount} account(s) rate-limited. Waiting ${formatDuration(allWaitMs)}...`); await sleep(allWaitMs); + + // Add small buffer after waiting to ensure rate limits have truly expired + await sleep(500); accountManager.clearExpiredLimits(); account = accountManager.pickNext(model); + + // If still no account after waiting, try optimistic reset + // This handles cases where the API rate limit is transient + if (!account) { + logger.warn('[CloudCode] No account available after wait, attempting optimistic reset...'); + accountManager.resetAllRateLimits(); + account = accountManager.pickNext(model); + } } if (!account) { @@ -264,10 +275,10 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb } if (isNetworkError(error)) { - logger.warn(`[CloudCode] Network error for ${account.email} (stream), trying next account... (${error.message})`); - await sleep(1000); // Brief pause before retry - accountManager.pickNext(model); // Advance to next account - continue; + logger.warn(`[CloudCode] Network error for ${account.email} (stream), trying next account... (${error.message})`); + await sleep(1000); // Brief pause before retry + accountManager.pickNext(model); // Advance to next account + continue; } throw error;