abhigyanpatwari/GitNexus · error · Error

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

Error message

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

What it means

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).

Source

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

  }

  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 &&
      response.status === 400 &&
      (errorText.includes('content_filter') || errorText.includes('ResponsibleAIPolicyViolation'))
    ) {
      throw new Error(
        `Azure content filter blocked this request. The prompt triggered content policy. Details: ${errorText.slice(0, 300)}`,
      );
    }

    // Any other non-OK response here is a terminal 4xx — resilientFetch
    // already retried 5xx/429 to exhaustion and would have thrown above.
    throw new Error(`LLM API error (${response.status}): ${errorText.slice(0, 500)}`);
  }

  // Streaming path
  if (useStream && response.body) {
    return await readSSEStream(response.body, options!.onChunk!);
  }

  // Non-streaming path
  const json = (await response.json()) as any;
  const choice = json.choices?.[0];
  if (!choice?.message?.content) {
    throw new Error('LLM returned empty response');
  }

  return {
    content: choice.message.content,
    promptTokens: json.usage?.prompt_tokens,
    completionTokens: json.usage?.completion_tokens,

View on GitHub (pinned to d540b00184)

Solutions

  1. Inspect the status code and the 500-char body in the message — they identify the problem.
  2. For 400/422: check the request body — drop temperature for reasoning models, ensure max_completion_tokens is set, validate the provider's schema.
  3. For 401/403: fix the API key and the auth scheme (Azure uses api-key header, others use Bearer).
  4. For 404: verify baseUrl path and model id.
  5. If the body shows a transient cause that should have been retried, file a bug — resilientFetch should have caught it.

Example fix

// before
await callLLM(prompt, { baseUrl, apiKey, model: 'o1-preview', temperature: 0.7 });
// 400: reasoning models reject temperature

// after
await callLLM(prompt, { baseUrl, apiKey, model: 'o1-preview' }); // no temperature
Defensive patterns

Strategy: try-catch

Validate before calling

async function probeProviderSchema(baseUrl, apiKey, model) {
  // tiny request that should be 200 if schema is right
  const r = await fetch(baseUrl.replace(/\/+$/, '') + '/chat/completions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + apiKey },
    body: JSON.stringify({ model, messages: [{ role: 'user', content: 'ping' }], max_completion_tokens: 1 }),
  });
  return r.status;
}

Type guard

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

Try / catch

try { return await callLLM(prompt, config); }
catch (e) {
  const s = terminalStatus(e);
  if (s === 400 || s === 422) { /* drop unsupported param (e.g. temperature on reasoning) */ }
  else if (s === 401 || s === 403) { /* fix auth header */ }
  else if (s === 404) { /* fix baseUrl path / model id */ }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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