abhigyanpatwari/GitNexus · error · Error

LLM API error (${err.response.status} after retries): ${erro

Error message

LLM API error (${err.response.status} after retries): ${errorText.slice(0, 500)}

What it means

Thrown when callLLM() catches a ResilientFetchExhaustedError: resilientFetch retried the request up to config.maxAttempts (default 3) against a non-OK HTTP response and gave up. The message includes the final HTTP status code and up to 500 chars of the response body so you can see the provider's own error text. Retriable statuses (5xx, 429) were already attempted; this catch typically means a hard failure.

Source

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

        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;
  }

  if (!response.ok) {
    const errorText = await response.text().catch(() => 'unknown error');

    // Azure content filter — surface a clear message instead of a generic API error.
    if (
      azure &&

View on GitHub (pinned to d540b00184)

Solutions

  1. Read the 500-char body slice in the message — it is the provider's own explanation (e.g. 'invalid_api_key', 'model_not_found').
  2. For 401/403: verify the API key env var (GITNEXUS_LLM_API_KEY / OPENAI_API_KEY) is set, valid, and has credit/quota.
  3. For 404: confirm the baseUrl path (most need /v1) and the exact model id the provider exposes.
  4. For 429: lower concurrency, raise config.maxAttempts, or add backoff via requestTimeoutMs.
  5. For 5xx: check the provider status page; the breaker (error 221) may also open.

Example fix

// before
config = { baseUrl: 'https://api.openai.com', model: 'gpt-4o', apiKey };
// 404: missing /v1, or model id typo

// after
config = { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini', apiKey };
Defensive patterns

Strategy: try-catch

Validate before calling

async function preflight(baseUrl, apiKey, model) {
  const r = await fetch(new URL('/models', baseUrl), { headers: { Authorization: 'Bearer ' + apiKey } });
  if (!r.ok) throw new Error(`preflight ${r.status}`);
  const ids = (await r.json()).data.map(m => m.id);
  if (!ids.includes(model)) throw new Error(`unknown model ${model}`);
}

Type guard

function isApiStatusError(e) {
  return e instanceof Error && /LLM API error \(\d+ after retries\)/.test(e.message);
}
function apiStatus(e) { const m = /\((\d+) after retries\)/.exec(e.message||''); return m ? +m[1] : null; }

Try / catch

try { return await callLLM(prompt, config); }
catch (e) {
  const s = apiStatus(e);
  if (s === 401 || s === 403) { /* refresh key */ }
  else if (s === 404) { /* fix baseUrl/model */ }
  else if (s === 429) { await sleep(5000); return await callLLM(prompt, config); }
  throw e;
}

Prevention

When it happens

Trigger: Provider returns 401/403 (bad/expired API key), 404 (wrong base URL or model name), 400 (malformed request body), or persistent 5xx/429 after all retries. Example: baseUrl='https://api.openai.com/v1' with model='gpt-9-fake' yields 'LLM API error (404 after retries): The model `gpt-9-fake` does not exist'.

Common situations: Wrong API key or expired token; model name typo or a model the account lacks access to; baseUrl missing the /v1 suffix or pointing at the wrong deployment; provider returning 429 because the org hit a quota; Azure deployment name mismatch.

Related errors


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