{"record":{"id":"8a1335ea75e00bb1","repo":"abhigyanpatwari/GitNexus","slug":"llm-api-error-response-status-errortext-sl","errorCode":null,"errorMessage":"LLM API error (${response.status}): ${errorText.slice(0, 500)}","messagePattern":"LLM API error \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/wiki/llm-client.ts","lineNumber":444,"sourceCode":"  }\n\n  if (!response.ok) {\n    const errorText = await response.text().catch(() => 'unknown error');\n\n    // Azure content filter — surface a clear message instead of a generic API error.\n    if (\n      azure &&\n      response.status === 400 &&\n      (errorText.includes('content_filter') || errorText.includes('ResponsibleAIPolicyViolation'))\n    ) {\n      throw new Error(\n        `Azure content filter blocked this request. The prompt triggered content policy. Details: ${errorText.slice(0, 300)}`,\n      );\n    }\n\n    // Any other non-OK response here is a terminal 4xx — resilientFetch\n    // already retried 5xx/429 to exhaustion and would have thrown above.\n    throw new Error(`LLM API error (${response.status}): ${errorText.slice(0, 500)}`);\n  }\n\n  // Streaming path\n  if (useStream && response.body) {\n    return await readSSEStream(response.body, options!.onChunk!);\n  }\n\n  // Non-streaming path\n  const json = (await response.json()) as any;\n  const choice = json.choices?.[0];\n  if (!choice?.message?.content) {\n    throw new Error('LLM returned empty response');\n  }\n\n  return {\n    content: choice.message.content,\n    promptTokens: json.usage?.prompt_tokens,\n    completionTokens: json.usage?.completion_tokens,","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/wiki/llm-client.ts#L426-L462","documentation":"The catch-all terminal-error path in callLLM(): resilientFetch returned a Response whose .ok is false (HTTP >= 400), it was not the Azure content-filter case, and resilientFetch already exhausted retries for transient statuses. The message includes the HTTP status and up to 500 chars of body. This is reached for non-retryable 4xx that resilientFetch chose not to retry (e.g. 400/401/403/404/422).","triggerScenarios":"Provider returns a 4xx that is not retried: 400 malformed body, 401/403 auth, 404 not found, 422 unprocessable, or a custom 4xx from a proxy. Because resilientFetch only retries 5xx/429, such responses fall through to response.ok===false and hit this throw.","commonSituations":"Wrong Authorization header format for the provider; unsupported request parameter (e.g. sending temperature to a reasoning model); proxy returning 407 auth required; baseUrl missing path component; model id rejected with 422.","solutions":["Inspect the status code and the 500-char body in the message — they identify the problem.","For 400/422: check the request body — drop temperature for reasoning models, ensure max_completion_tokens is set, validate the provider's schema.","For 401/403: fix the API key and the auth scheme (Azure uses api-key header, others use Bearer).","For 404: verify baseUrl path and model id.","If the body shows a transient cause that should have been retried, file a bug — resilientFetch should have caught it."],"exampleFix":"// before\nawait callLLM(prompt, { baseUrl, apiKey, model: 'o1-preview', temperature: 0.7 });\n// 400: reasoning models reject temperature\n\n// after\nawait callLLM(prompt, { baseUrl, apiKey, model: 'o1-preview' }); // no temperature","handlingStrategy":"try-catch","validationCode":"async function probeProviderSchema(baseUrl, apiKey, model) {\n  // tiny request that should be 200 if schema is right\n  const r = await fetch(baseUrl.replace(/\\/+$/, '') + '/chat/completions', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + apiKey },\n    body: JSON.stringify({ model, messages: [{ role: 'user', content: 'ping' }], max_completion_tokens: 1 }),\n  });\n  return r.status;\n}","typeGuard":"function isTerminalApiError(e) {\n  return e instanceof Error && /LLM API error \\(\\d+\\):/.test(e.message);\n}\nfunction terminalStatus(e) { const m = /LLM API error \\((\\d+)\\):/.exec(e.message||''); return m ? +m[1] : null; }","tryCatchPattern":"try { return await callLLM(prompt, config); }\ncatch (e) {\n  const s = terminalStatus(e);\n  if (s === 400 || s === 422) { /* drop unsupported param (e.g. temperature on reasoning) */ }\n  else if (s === 401 || s === 403) { /* fix auth header */ }\n  else if (s === 404) { /* fix baseUrl path / model id */ }\n  throw e;\n}","preventionTips":["Probe a 1-token request first to validate schema/auth before long generation.","Match the auth scheme to the provider (Azure: api-key header; others: Bearer).","Do not send temperature to reasoning models; do not omit max_completion_tokens."],"tags":["llm","network","api-error","wiki"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}