ruvnet/ruflo · error

Circuit breaker ${this.name} is open

Error message

Circuit breaker ${this.name} is open

What it means

Every provider built on BaseProvider wraps its doComplete/doStreamComplete calls in a CircuitBreaker. After `threshold` consecutive failures (default 5) the breaker flips to 'open' and this error is thrown immediately, without contacting the upstream API. Once `resetTimeout` ms (default 60000) elapse since the last failure, the next call transitions the breaker to 'half-open' and lets a trial request through.

Source

Thrown at v3/@claude-flow/providers/src/base-provider.ts:53

 * Simple circuit breaker implementation
 */
class CircuitBreaker {
  private failures = 0;
  private lastFailure = 0;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  constructor(
    private readonly name: string,
    private readonly threshold: number = 5,
    private readonly resetTimeout: number = 60000
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailure > this.resetTimeout) {
        this.state = 'half-open';
      } else {
        throw new Error(`Circuit breaker ${this.name} is open`);
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failures = 0;
    this.state = 'closed';
  }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Wait out the reset window (default 60 s) before the next call - the breaker then moves to half-open and allows one trial request; do not retry immediately
  2. Fix the underlying failures that opened it: inspect logs for the AuthenticationError / RateLimitError / LLMProviderError thrown just before the breaker opened
  3. Tune threshold and resetTimeout in the provider options if 5 failures / 60 s trips too easily for your traffic shape
  4. Recreate or re-initialize the provider to get a fresh breaker once you know upstream is healthy again

Example fix

// before
const provider = manager.getProvider('openai');
await provider.complete(req); // throws: Circuit breaker openai is open

// after - back off for the reset window, then retry once (half-open trial)
try {
  await provider.complete(req);
} catch (e) {
  if (e instanceof Error && e.message.includes('Circuit breaker')) {
    await new Promise(r => setTimeout(r, 60_000));
    await provider.complete(req);
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: retry

Type guard

export function isCircuitBreakerOpen(e: unknown): boolean {
  return e instanceof Error && /^Circuit breaker .+ is open$/.test(e.message);
}

Try / catch

try {
  return await provider.complete(req);
} catch (e) {
  if (isCircuitBreakerOpen(e)) {
    await new Promise(r => setTimeout(r, 60_000 + Math.random() * 5_000)); // resetTimeout + jitter
    return provider.complete(req); // single half-open trial retry
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling provider.complete() or provider.streamComplete() while that provider instance's breaker is 'open' and fewer than 60 s (resetTimeout) have passed since the last failure - e.g. the upstream API returned 429/5xx/timeout five times in a row and you retry a sixth time within the window.

Common situations: Sustained upstream outage or aggressive rate limiting that trips the breaker; a tight retry loop hammering the provider; config.timeout set too small so every call times out; one shared provider instance absorbing failures from many concurrent requests.

Related errors


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