ruvnet/ruflo · warning

Provider ${provider.id} is rate limited

Error message

Provider ${provider.id} is rate limited

What it means

ProviderAdapter's per-provider rate-limit guard throws when a provider's status is 'rate-limited' and the 60-second window (rateLimits.resetAt) has not elapsed. Requests are refused before dispatch to protect the provider instead of being sent upstream to fail.

Source

Thrown at v3/@claude-flow/integration/src/provider-adapter.ts:926

  }

  /**
   * Check rate limits before request
   */
  private checkRateLimits(provider: Provider): void {
    // Reset rate limits if needed
    const now = Date.now();
    if (now >= provider.rateLimits.resetAt) {
      provider.rateLimits.currentRequests = 0;
      provider.rateLimits.currentTokens = 0;
      provider.rateLimits.resetAt = now + 60000;
      if (provider.status === 'rate-limited') {
        provider.status = 'available';
      }
    }

    if (provider.status === 'rate-limited') {
      throw new Error(`Provider ${provider.id} is rate limited`);
    }
  }

  /**
   * Check cost limits
   */
  private checkCostLimits(): void {
    const now = Date.now();

    // Reset hourly cost if needed
    if (now >= this.hourlyCost.resetAt) {
      this.hourlyCost.amount = 0;
      this.hourlyCost.resetAt = now + 3600000;
    }

    if (this.hourlyCost.amount >= this.config.costLimitPerHour!) {
      throw new Error(
        `Hourly cost limit exceeded: $${this.hourlyCost.amount.toFixed(2)} / $${this.config.costLimitPerHour}`

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Back off until rateLimits.resetAt (at most 60s into the window) and resubmit
  2. Throttle or queue upstream (rate limiter with jitter) so bursts stay under the provider's configured limits
  3. Raise the provider's configured rateLimits if the account actually permits more throughput
  4. Register or enable additional providers so selection can route around a limited one

Example fix

// before
const results = await Promise.all(tasks.map(t => adapter.execute(t))); // synchronized burst trips the limit

// after
import pLimit from 'p-limit';
const limit = pLimit(Math.floor(providerLimits.requestsPerMinute / 60)); // ~ steady rate
const results = await Promise.all(tasks.map(t => limit(() => adapter.execute(t))));
Defensive patterns

Strategy: retry

Validate before calling

// Own the pacing: cap in-flight calls per provider under its configured limit
const gate = new Semaphore(Math.floor(providerRateLimits.maxRequestsPerMinute / 60));
const run = () => gate.with(() => adapter.execute(task));

Try / catch

try {
  await adapter.execute(task);
} catch (e) {
  if (e instanceof Error && /rate limited/.test(e.message)) {
    await sleep(msUntilNextMinuteWindow()); // window is 60s per rateLimits.resetAt
    return adapter.execute(task); // exactly one rebounce, not a tight loop
  }
  throw e;
}

Prevention

When it happens

Trigger: Executing or routing a task to a provider whose rateLimits counters tripped the 'rate-limited' status earlier in the same window (now < rateLimits.resetAt); the status only flips back to 'available' once the window resets (resetAt = last reset + 60000ms).

Common situations: Bursty fan-out (Promise.all over many tasks or workers) exceeding the provider's configured request/token limits; limits configured lower than the account allows; a shared provider saturated by another component of the system.

Related errors


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