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
- Read the 500-char body slice in the message — it is the provider's own explanation (e.g. 'invalid_api_key', 'model_not_found').
- For 401/403: verify the API key env var (GITNEXUS_LLM_API_KEY / OPENAI_API_KEY) is set, valid, and has credit/quota.
- For 404: confirm the baseUrl path (most need /v1) and the exact model id the provider exposes.
- For 429: lower concurrency, raise config.maxAttempts, or add backoff via requestTimeoutMs.
- 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
- Preflight the API key and model id against GET /models before the long generation.
- Ensure baseUrl ends with the provider-required path (usually /v1).
- Log the 500-char body slice — it is the provider's own diagnosis.
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
- LLM endpoint circuit open: retry in ${Math.ceil(err.retryAft
- LLM API error (${response.status}): ${errorText.slice(0, 500
- LLM request timed out after ${formatTimeoutDuration(config.r
- LLM returned empty response
- Request failed after retries (HTTP ${response.status})
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/c7ab6fc53b766880.
Report an issue: GitHub.