abhigyanpatwari/GitNexus · error · CircuitOpenError
Circuit '${key}' is open; retry in ${Math.ceil(halfOpenRetry
Error message
Circuit '${key}' is open; retry in ${Math.ceil(halfOpenRetryAfterMs / 1000)}s What it means
Thrown by CircuitBreaker.check() when the breaker is 'half-open' (cooldown elapsed, probing recovery) but the single probe permit is already held by another in-flight call. Half-open admits exactly one concurrent probe to prevent the thundering-herd that would defeat fail-fast during recovery; concurrent callers get this error with retryAfterMs = halfOpenRetryAfterMs (default 1000ms), distinct from the remaining-cooldown value of the open-state error.
Source
Thrown at gitnexus-shared/src/integrations/circuit-breaker.ts:166
* Failing to pair leaves the probe permit consumed forever and
* wedges the breaker. See file-header JSDoc for the canonical
* try/finally pattern.
*/
check(): void {
if (this.state === 'open' && this.openedAt !== null) {
const elapsed = this.now() - this.openedAt;
if (elapsed < this.cooldownMs) {
throw new CircuitOpenError(this.cooldownMs - elapsed, this.key);
}
// Cooldown elapsed — transition to Half-Open. The very next
// `probeInFlight` check below decides whether THIS caller gets
// the permit or hits the gate.
this.state = 'half-open';
}
if (this.state === 'half-open') {
if (this.probeInFlight) {
throw new CircuitOpenError(this.halfOpenRetryAfterMs, this.key);
}
this.probeInFlight = true;
}
// Closed state falls through silently.
}
recordSuccess(): void {
this.probeInFlight = false;
this.consecutiveFailures = 0;
this.state = 'closed';
this.openedAt = null;
}
recordFailure(): void {
this.probeInFlight = false;
this.consecutiveFailures += 1;
if (this.state === 'half-open' || this.consecutiveFailures >= this.failureThreshold) {
this.state = 'open';View on GitHub (pinned to d540b00184)
Solutions
- Retry after halfOpenRetryAfterMs (default 1000ms) — the probe will have resolved and either closed or re-opened the breaker
- Raise halfOpenRetryAfterMs in CircuitBreakerOptions if the protected op is long-running (LLM streaming, large uploads) so the suggested wait matches reality
- Serialize calls to the recovering dependency until the probe resolves, rather than firing concurrent retries
- Avoid retry storms: do not retry in a tight loop — the error's retryAfterMs is the floor
Example fix
// before — concurrent callers all retry at once, re-stampeding the probe
async function call(breaker, op) {
breaker.check();
return op();
}
// after — back off by halfOpenRetryAfterMs when the probe is busy
try { breaker.check(); }
catch (e) {
if (e instanceof CircuitOpenError) {
await new Promise(r => setTimeout(r, e.retryAfterMs));
breaker.check();
} else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
import { getBreaker } from 'gitnexus-shared/src/integrations/circuit-breaker.js';
const breaker = getBreaker('my-key');
// getState() returns 'half-open' if cooldown elapsed; isProbeInFlight() tells if a probe is outstanding
if (breaker.getState() === 'half-open' && breaker.isProbeInFlight()) {
console.log('probe in flight; back off ~1s');
} Type guard
import { CircuitOpenError } from 'gitnexus-shared/src/integrations/circuit-breaker.js';
function isHalfOpenBlocked(e: unknown): e is CircuitOpenError {
return e instanceof CircuitOpenError; // distinguish from open-cooldown by retryAfterMs magnitude
} Try / catch
try {
breaker.check();
} catch (e) {
if (e instanceof CircuitOpenError) {
// halfOpenRetryAfterMs (default 1s) — back off, don't stampede
await new Promise(r => setTimeout(r, e.retryAfterMs));
return retry();
}
throw e;
} Prevention
- Serialize calls to a recovering dependency until the probe resolves to avoid pile-up
- Set halfOpenRetryAfterMs to match the protected op's typical duration so the suggested wait is accurate
- Never retry in a tight synchronous loop — honor retryAfterMs as the floor
- Queue or shed load during half-open rather than fanning out concurrent probes
When it happens
Trigger: Two or more concurrent calls to breaker.check()/resilientFetch against the same breaker key while state==='half-open' and probeInFlight===true. Also fires for the caller that observed the Open→Half-Open transition but lost the microtask race to a peer that grabbed the permit first.
Common situations: Recovery from an outage: cooldown elapsed, the first probe is a slow operation (LLM stream, large upload), and concurrent UI/API requests pile up behind it. Spikes during partial recovery of a flaky backend where only one request is allowed through to test health.
Related errors
- Circuit '${key}' is open; retry in ${Math.ceil(retryAfterMs
- hf-circuit-open: HuggingFace download circuit is open after
- hf-circuit-open: HuggingFace download circuit opened after 3
- Worker pool circuit breaker tripped${reason}. Subsequent dis
- LLM endpoint circuit open: retry in ${Math.ceil(err.retryAft
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/02a26bd74002e9f4.
Report an issue: GitHub.