srbhr/Resume-Matcher · error · Error

${message = data.detail or Failed to update LLM config (stat

Error message

${message = data.detail or Failed to update LLM config (status ${res.status}).}

What it means

updateLlmConfig throws an Error after a non-OK response from PUT/POST /config/llm-api-key. Unlike fetchLlmConfig it first tries to parse the JSON body and use its `detail` field (FastAPI-style error payload), falling back to a generic status message; non-string details are JSON.stringify'd.

Source

Thrown at apps/frontend/lib/api/config.ts:106

    credentials: 'include',
    body: JSON.stringify(config),
  });

  if (!res.ok) {
    const data = (await res.json().catch(() => ({}))) as { detail?: unknown };
    // FastAPI returns `detail` as a string OR a structured object (this
    // endpoint now emits {code, field, missing} for a missing Base URL).
    // Passing an object straight to `new Error()` renders "[object Object]",
    // so serialize explicitly — same treatment as updateFeaturePrompts.
    let message: string;
    if (typeof data.detail === 'string') {
      message = data.detail;
    } else if (data.detail) {
      message = JSON.stringify(data.detail);
    } else {
      message = `Failed to update LLM config (status ${res.status}).`;
    }
    throw new Error(message);
  }

  return res.json();
}

// Legacy function for backwards compatibility
export async function updateLlmApiKey(value: string): Promise<string> {
  const config = await updateLlmConfig({ api_key: value });
  return config.api_key ?? '';
}

// Test LLM connection with optional config (for pre-save testing)
export async function testLlmConnection(config?: LLMConfigUpdate): Promise<LLMHealthCheck> {
  const options: RequestInit = {
    method: 'POST',
    credentials: 'include',
  };

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Read the thrown message — a FastAPI `detail` string names the exact invalid field; correct that field in the settings form
  2. Re-login if the status is 401/403 (session expired while the form was open)
  3. Validate the API key/provider/model combination client-side before saving
  4. Check backend logs and its request-validation schema if the message is the generic fallback

Example fix

// before
await updateLlmConfig({ provider: 'openai', model: 'gpt-4', apiKey });
// after
if (!apiKey.startsWith('sk-')) { showMsg('Invalid OpenAI API key'); return; }
try { await updateLlmConfig(config); }
catch (e) { showMsg(e.message); }
Defensive patterns

Strategy: validation

Validate before calling

function validateLlmConfig(c: LLMConfig): string | null {
  if (!c.provider) return 'provider required';
  if (!c.apiKey || c.apiKey.length < 8) return 'apiKey missing/short';
  if (!c.model) return 'model required';
  return null;
}

Type guard

function hasDetail(x: unknown): x is { detail: string } {
  return typeof x === 'object' && x !== null && 'detail' in x && typeof (x as any).detail === 'string';
}

Try / catch

try {
  await updateLlmConfig(cfg);
} catch (e) {
  // message already carries backend `detail` when available
  showToast(e instanceof Error ? e.message : 'Save failed');
}

Prevention

When it happens

Trigger: Saving LLM settings when the backend rejects them: 422 invalid provider/model/api-key shape, 401 unauthenticated, 400 invalid key format, or 500 persistence failure. Also fires when the response body is not JSON (data parse fails -> generic message).

Common situations: Typing a malformed API key or selecting a model not valid for the chosen provider; session expired mid-edit; backend validation schema changed after an upgrade.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/28942100785b0f5e. Report an issue: GitHub.