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

  1. Retry on RateLimitError with exponential backoff plus jitter; do not retry immediately
  2. Throttle concurrency client-side (p-limit, queue) so sustained RPM stays under the quota
  3. Move to a paid/billed project or request a quota increase if traffic legitimately exceeds the tier
  4. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/1a8cd0c5a40899ac. Report an issue: GitHub.