abhigyanpatwari/GitNexus · error · Error

LLM endpoint circuit open: retry in ${Math.ceil(err.retryAft

Error message

LLM endpoint circuit open: retry in ${Math.ceil(err.retryAfterMs / 1000)}s. ${err.message}

What it means

Thrown when callLLM() catches a CircuitOpenError from resilientFetch(). GitNexus wraps each LLM host in an in-process circuit breaker keyed 'wiki-llm-<host>'; after enough consecutive failures the breaker opens and fails fast instead of hammering a sick endpoint. The message surfaces the remaining cool-down (retryAfterMs rounded up to seconds) plus the underlying cause. This protects both your run and the upstream provider from a retry storm.

Source

Thrown at gitnexus/src/core/wiki/llm-client.ts:409

          ...authHeaders,
        },
        body: JSON.stringify(body),
        // Request timeout is opt-in for wiki generation. Large local
        // model runs can legitimately take well over a minute, so the
        // default runtime path must not impose a hidden 60s ceiling.
        signal:
          config.requestTimeoutMs !== undefined
            ? AbortSignal.timeout(config.requestTimeoutMs)
            : undefined,
      },
      {
        breakerKey: `wiki-llm-${new URL(url).host}`,
        retry: { maxAttempts: config.maxAttempts ?? 3, baseDelayMs: 2_000, capDelayMs: 30_000 },
      },
    );
  } catch (err) {
    if (err instanceof CircuitOpenError) {
      throw new Error(
        `LLM endpoint circuit open: retry in ${Math.ceil(err.retryAfterMs / 1000)}s. ${err.message}`,
      );
    }
    if (err instanceof ResilientFetchExhaustedError) {
      const errorText = await err.response.text().catch(() => 'unknown error');
      throw new Error(
        `LLM API error (${err.response.status} after retries): ${errorText.slice(0, 500)}`,
      );
    }
    if (config.requestTimeoutMs !== undefined && isTimeoutLikeError(err)) {
      throw new Error(
        `LLM request timed out after ${formatTimeoutDuration(config.requestTimeoutMs)}. ` +
          'Increase --timeout or omit it to disable the request timeout.',
      );
    }
    throw err;
  }

View on GitHub (pinned to d540b00184)

Solutions

  1. Wait the indicated cool-down (retryAfterMs) before retrying — the breaker auto-closes after it elapses.
  2. Check the underlying err.message echoed in the text to fix the root cause (API key, model name, endpoint health).
  3. Verify the provider status page and the endpoint reachability (curl the /models endpoint).
  4. Restart a crashed local model server, or point baseUrl at a healthy host (different breaker key).
  5. If the breaker opens during long-running generation, lower config.maxAttempts or add requestTimeoutMs to surface timeouts sooner rather than burning attempts.

Example fix

// before
for (const p of prompts) {
  await callLLM(p, config); // hammers a sick endpoint, trips breaker
}

// after: respect cool-down + jitter
async function callWithBreaker(p) {
  try { return await callLLM(p, config); }
  catch (e) {
    const m = /retry in (\d+)s/.exec(e.message);
    if (m) await sleep((+m[1]) * 1000 + 500);
    throw e;
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// No pre-call check exists; circuit state is internal. Probe endpoint health first:
async function endpointHealthy(baseUrl) {
  try {
    const r = await fetch(new URL('/models', baseUrl), { headers: { Authorization: 'Bearer ' + key } });
    return r.ok || r.status === 404; // 404 = wrong path but reachable
  } catch { return false; }
}

Type guard

function isCircuitOpenError(e) {
  return e instanceof Error && /circuit open: retry in \d+s/.test(e.message);
}
function circuitRetrySeconds(e) {
  const m = /retry in (\d+)s/.exec(e.message || '');
  return m ? +m[1] : null;
}

Try / catch

try { return await callLLM(prompt, config); }
catch (e) {
  if (isCircuitOpenError(e)) {
    const s = circuitRetrySeconds(e);
    if (s) await new Promise(r => setTimeout(r, s * 1000 + 500));
    return await callLLM(prompt, config); // one retry after cool-down
  }
  throw e;
}

Prevention

When it happens

Trigger: Three or more consecutive failed LLM calls to the same host within the breaker window (e.g. repeated 5xx, repeated auth failures, or repeated timeouts). The next callLLM() short-circuits before any network request and you see 'circuit open: retry in Ns'. Reproducible by pointing at an endpoint with a bad API key and forcing several calls quickly.

Common situations: LLM provider regional outage; wrong API key returning 401/403 every attempt; sustained 429 rate limiting that exhausts retries; local model server (Ollama) crashed but baseUrl still points at it; CI run hammering the endpoint after maxAttempts=3 each.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/f0bebf9dc64ed4c7. Report an issue: GitHub.