micro/go-micro · warning

circuit breaker open (consecutive failures: %d)

Error message

circuit breaker open (consecutive failures: %d)

What it means

The circuit breaker's Allow() denies calls while the circuit is open after reaching the consecutive-failure threshold. It only admits a probe once timeout has elapsed since the last failure (moving to half-open); until then every call returns this error. It protects the downstream target from being hammered while it is unhealthy.

Source

Thrown at gateway/mcp/circuitbreaker.go:97

	}
}

// Allow checks whether a request should be allowed through.
// Returns nil if allowed, error if the circuit is open.
func (cb *circuitBreaker) Allow() error {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case circuitClosed:
		return nil
	case circuitOpen:
		if time.Since(cb.lastFailure) > cb.timeout {
			cb.state = circuitHalfOpen
			cb.halfOpenUsed = 0
			return nil
		}
		return fmt.Errorf("circuit breaker open (consecutive failures: %d)", cb.failures)
	case circuitHalfOpen:
		if cb.halfOpenUsed < cb.maxHalfOpen {
			cb.halfOpenUsed++
			return nil
		}
		return fmt.Errorf("circuit breaker half-open (probe limit reached)")
	}
	return nil
}

// RecordSuccess records a successful call. If half-open, closes the circuit.
func (cb *circuitBreaker) RecordSuccess() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	cb.failures = 0
	cb.state = circuitClosed
}

View on GitHub (pinned to 24529f1404)

Solutions

  1. Wait until the breaker's timeout elapses; the next Allow() transitions to half-open and admits a probe.
  2. Fix the underlying target failures that tripped the breaker (connectivity, endpoint health).
  3. Tune breaker configuration (failure threshold, timeout) to match the target's realistic recovery time.
  4. Treat the error as a signal to shed load / return a cached or degraded response instead of retrying immediately.

Example fix

// before
for {
    if err := cb.Allow(); err != nil { continue } // hot loop against open breaker
}
// after
if err := cb.Allow(); err != nil {
    time.Sleep(cbBackoff) // back off until the open window expires
    continue
}
Defensive patterns

Strategy: retry

Validate before calling

if cb.IsOpen() { // if exposed; otherwise track failures yourself
    return ErrDegraded
}

Type guard

func isBreakerOpen(err error) bool {
    return err != nil && strings.Contains(err.Error(), "circuit breaker open")
}

Try / catch

if err := cb.Allow(); err != nil {
    if isBreakerOpen(err) {
        return serveFallback() // cached/degraded response
    }
    return err
}
defer cb.RecordResult(callerErr == nil)

Prevention

When it happens

Trigger: Calling Allow() while the breaker is in the open state and time.Since(lastFailure) <= cb.timeout, i.e. the failure threshold was tripped and the cool-down window has not expired.

Common situations: An MCP target repeatedly failing (network outage, bad endpoint) trips the breaker; callers keep invoking tools during the cool-down; short timeout configured so the open window lasts longer than the caller expects.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/ce003363773b1c33. Report an issue: GitHub.