{"record":{"id":"02a26bd74002e9f4","repo":"abhigyanpatwari/GitNexus","slug":"circuit-key-is-open-retry-in-math-ceil-hal","errorCode":null,"errorMessage":"Circuit '${key}' is open; retry in ${Math.ceil(halfOpenRetryAfterMs / 1000)}s","messagePattern":"Circuit '(.+?)' is open; retry in (.+?)s","errorType":"exception","errorClass":"CircuitOpenError","httpStatus":null,"severity":"error","filePath":"gitnexus-shared/src/integrations/circuit-breaker.ts","lineNumber":166,"sourceCode":"   * Failing to pair leaves the probe permit consumed forever and\n   * wedges the breaker. See file-header JSDoc for the canonical\n   * try/finally pattern.\n   */\n  check(): void {\n    if (this.state === 'open' && this.openedAt !== null) {\n      const elapsed = this.now() - this.openedAt;\n      if (elapsed < this.cooldownMs) {\n        throw new CircuitOpenError(this.cooldownMs - elapsed, this.key);\n      }\n      // Cooldown elapsed — transition to Half-Open. The very next\n      // `probeInFlight` check below decides whether THIS caller gets\n      // the permit or hits the gate.\n      this.state = 'half-open';\n    }\n\n    if (this.state === 'half-open') {\n      if (this.probeInFlight) {\n        throw new CircuitOpenError(this.halfOpenRetryAfterMs, this.key);\n      }\n      this.probeInFlight = true;\n    }\n    // Closed state falls through silently.\n  }\n\n  recordSuccess(): void {\n    this.probeInFlight = false;\n    this.consecutiveFailures = 0;\n    this.state = 'closed';\n    this.openedAt = null;\n  }\n\n  recordFailure(): void {\n    this.probeInFlight = false;\n    this.consecutiveFailures += 1;\n    if (this.state === 'half-open' || this.consecutiveFailures >= this.failureThreshold) {\n      this.state = 'open';","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus-shared/src/integrations/circuit-breaker.ts#L148-L184","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before — concurrent callers all retry at once, re-stampeding the probe\nasync function call(breaker, op) {\n  breaker.check();\n  return op();\n}\n\n// after — back off by halfOpenRetryAfterMs when the probe is busy\ntry { breaker.check(); }\ncatch (e) {\n  if (e instanceof CircuitOpenError) {\n    await new Promise(r => setTimeout(r, e.retryAfterMs));\n    breaker.check();\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"import { getBreaker } from 'gitnexus-shared/src/integrations/circuit-breaker.js';\nconst breaker = getBreaker('my-key');\n// getState() returns 'half-open' if cooldown elapsed; isProbeInFlight() tells if a probe is outstanding\nif (breaker.getState() === 'half-open' && breaker.isProbeInFlight()) {\n  console.log('probe in flight; back off ~1s');\n}","typeGuard":"import { CircuitOpenError } from 'gitnexus-shared/src/integrations/circuit-breaker.js';\nfunction isHalfOpenBlocked(e: unknown): e is CircuitOpenError {\n  return e instanceof CircuitOpenError; // distinguish from open-cooldown by retryAfterMs magnitude\n}","tryCatchPattern":"try {\n  breaker.check();\n} catch (e) {\n  if (e instanceof CircuitOpenError) {\n    // halfOpenRetryAfterMs (default 1s) — back off, don't stampede\n    await new Promise(r => setTimeout(r, e.retryAfterMs));\n    return retry();\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["circuit-breaker","resilience","concurrency","thundering-herd"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}