ruvnet/ruflo · error · RateLimitError

RATE_LIMIT

RATE_LIMIT

Error message

${message}

What it means

OpenAIProvider maps HTTP 429 to RateLimitError (retryable, statusCode 429) and is the one mapping that parses the response's retry-after header into error.retryAfter (seconds). OpenAI returns 429 when org/project rate limits (RPM, TPM, IPU) or quota is exceeded.

Source

Thrown at v3/@claude-flow/providers/src/openai-provider.ts:471

  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:
        throw new AuthenticationError(message, 'openai', errorData);
      case 429:
        const retryAfter = response.headers.get('retry-after');
        throw new RateLimitError(
          message,
          'openai',
          retryAfter ? parseInt(retryAfter) : undefined,
          errorData
        );
      case 404:
        throw new ModelNotFoundError(this.config.model, 'openai', errorData);
      default:
        throw new LLMProviderError(
          message,
          `OPENAI_${response.status}`,
          'openai',
          response.status,
          response.status >= 500,
          errorData
        );
    }
  }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Honor error.retryAfter when present - sleep that many seconds before the next attempt
  2. Otherwise back off exponentially with jitter, and cap concurrent in-flight requests
  3. Enable the ProviderManager request cache so identical prompts do not re-bill and re-count
  4. Check usage and limits in the OpenAI dashboard; request an increase or upgrade the tier if sustained

Example fix

// before
const res = await provider.complete(req); // 429 propagates immediately

// after - respect retryAfter from the RateLimitError
catch (e) {
  if (e instanceof RateLimitError) {
    const wait = (e.retryAfter ?? 2 ** attempt) * 1000;
    await new Promise(r => setTimeout(r, wait));
    return provider.complete(req);
  }
  throw e;
}
Defensive patterns

Strategy: retry

Type guard

import { RateLimitError } from './types.js';
function isOpenAIRateLimit(e: unknown): e is RateLimitError {
  return e instanceof RateLimitError && e.provider === 'openai';
}

Try / catch

for (let attempt = 0; ; attempt++) {
  try {
    return await provider.complete(req);
  } catch (e) {
    if (isOpenAIRateLimit(e) && attempt < 5) {
      const waitMs = (e.retryAfter ?? 2 ** attempt) * 1000; // retryAfter parsed from header
      await new Promise(r => setTimeout(r, waitMs + Math.random() * 500));
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: Bursts of complete()/streamComplete() exceeding the org's requests-per-minute or tokens-per-minute limit; hitting a monthly billing quota; too many parallel requests from one key.

Common situations: Fan-out generation without throttling; batch jobs sharing one org across services; sudden traffic spike; tier limits too low for the workload.

Related errors


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