{"record":{"id":"ce003363773b1c33","repo":"micro/go-micro","slug":"circuit-breaker-open-consecutive-failures-d","errorCode":null,"errorMessage":"circuit breaker open (consecutive failures: %d)","messagePattern":"circuit breaker open \\(consecutive failures: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gateway/mcp/circuitbreaker.go","lineNumber":97,"sourceCode":"\t}\n}\n\n// Allow checks whether a request should be allowed through.\n// Returns nil if allowed, error if the circuit is open.\nfunc (cb *circuitBreaker) Allow() error {\n\tcb.mu.Lock()\n\tdefer cb.mu.Unlock()\n\n\tswitch cb.state {\n\tcase circuitClosed:\n\t\treturn nil\n\tcase circuitOpen:\n\t\tif time.Since(cb.lastFailure) > cb.timeout {\n\t\t\tcb.state = circuitHalfOpen\n\t\t\tcb.halfOpenUsed = 0\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"circuit breaker open (consecutive failures: %d)\", cb.failures)\n\tcase circuitHalfOpen:\n\t\tif cb.halfOpenUsed < cb.maxHalfOpen {\n\t\t\tcb.halfOpenUsed++\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"circuit breaker half-open (probe limit reached)\")\n\t}\n\treturn nil\n}\n\n// RecordSuccess records a successful call. If half-open, closes the circuit.\nfunc (cb *circuitBreaker) RecordSuccess() {\n\tcb.mu.Lock()\n\tdefer cb.mu.Unlock()\n\n\tcb.failures = 0\n\tcb.state = circuitClosed\n}","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/mcp/circuitbreaker.go#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait until the breaker's timeout elapses; the next Allow() transitions to half-open and admits a probe.","Fix the underlying target failures that tripped the breaker (connectivity, endpoint health).","Tune breaker configuration (failure threshold, timeout) to match the target's realistic recovery time.","Treat the error as a signal to shed load / return a cached or degraded response instead of retrying immediately."],"exampleFix":"// before\nfor {\n    if err := cb.Allow(); err != nil { continue } // hot loop against open breaker\n}\n// after\nif err := cb.Allow(); err != nil {\n    time.Sleep(cbBackoff) // back off until the open window expires\n    continue\n}","handlingStrategy":"retry","validationCode":"if cb.IsOpen() { // if exposed; otherwise track failures yourself\n    return ErrDegraded\n}","typeGuard":"func isBreakerOpen(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"circuit breaker open\")\n}","tryCatchPattern":"if err := cb.Allow(); err != nil {\n    if isBreakerOpen(err) {\n        return serveFallback() // cached/degraded response\n    }\n    return err\n}\ndefer cb.RecordResult(callerErr == nil)","preventionTips":["Always pair Allow() with RecordSuccess/RecordFailure.","Return a degraded response instead of hot-looping while open.","Size the breaker timeout to the target's realistic recovery time.","Add alerting on repeated breaker-open events."],"tags":["circuit-breaker","resilience","backpressure"],"backgroundTag":"circuit-breaker-open","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}