ruvnet/ruflo · error · RateLimitError
RATE_LIMIT
RATE_LIMIT
Error message
${message} What it means
GoogleProvider maps HTTP 429 to RateLimitError (retryable, statusCode 429, retryAfter undefined - Gemini's retry-after header is not parsed here). Google enforces per-project, per-model RPM/TPM quotas, with shared no-cost tier limits that are noticeably tight on popular Gemini models.
Source
Thrown at v3/@claude-flow/providers/src/google-provider.ts:417
private async handleErrorResponse(response: Response): Promise<never> {
const errorText = await response.text();
let errorData: { error?: { message?: string } };
try {
errorData = JSON.parse(errorText);
} catch {
errorData = { error: { message: errorText } };
}
const message = errorData.error?.message || 'Unknown error';
switch (response.status) {
case 401:
case 403:
throw new AuthenticationError(message, 'google', errorData);
case 429:
throw new RateLimitError(message, 'google', undefined, errorData);
default:
throw new LLMProviderError(
message,
`GOOGLE_${response.status}`,
'google',
response.status,
response.status >= 500,
errorData
);
}
}
}
View on GitHub (pinned to fa13ee4ad6)
Solutions
- Retry on RateLimitError with exponential backoff plus jitter; do not retry immediately
- Throttle concurrency client-side (p-limit, queue) so sustained RPM stays under the quota
- Move to a paid/billed project or request a quota increase if traffic legitimately exceeds the tier
- Spread load across models - quotas are per model, so alternating gemini variants can help
Example fix
// before
const results = await Promise.all(prompts.map(p => provider.complete({ prompt: p }))); // burst -> 429
// after - bounded concurrency + backoff on RateLimitError
const limit = pLimit(2);
const results = await Promise.all(prompts.map(p => limit(() =>
withBackoff(() => provider.complete({ prompt: p }), { on: RateLimitError })
))); Defensive patterns
Strategy: retry
Type guard
import { RateLimitError } from './types.js';
function isGoogleRateLimit(e: unknown): e is RateLimitError {
return e instanceof RateLimitError && e.provider === 'google';
} Try / catch
for (let attempt = 0; ; attempt++) {
try {
return await provider.complete(req);
} catch (e) {
if (isGoogleRateLimit(e) && attempt < 5) {
// retryAfter is undefined in this mapping - use own backoff
await new Promise(r => setTimeout(r, Math.min(60_000, 2 ** attempt * 1000) + Math.random() * 500));
continue;
}
throw e;
}
} Prevention
- Throttle to a sustained RPM below the project quota; per-model quotas mean spreading across models helps
- Cache identical completions (the ProviderManager supports request caching)
- Separate dev and prod projects so free-tier quotas are not shared
When it happens
Trigger: A burst of complete()/streamComplete() calls on a Gemini model exceeding the per-minute request or token quota for the project, especially on the free tier.
Common situations: Fan-out generation loops without throttling; polling or load-test traffic; free-tier quota shared across dev and prod because both use the same project key.
Related errors
- RATE_LIMIT
- RATE_LIMIT
- AUTHENTICATION
- GOOGLE_${response.status}
- MCP server "${server.name}" is in cooldown (HTTP ${cd.status
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/1a8cd0c5a40899ac.
Report an issue: GitHub.