{"record":{"id":"28942100785b0f5e","repo":"srbhr/Resume-Matcher","slug":"message-data-detail-or-failed-to-update-llm-co","errorCode":null,"errorMessage":"${message = data.detail or Failed to update LLM config (status ${res.status}).}","messagePattern":"\\$\\{message = data\\.detail or Failed to update LLM config \\(status \\$\\{res\\.status\\}\\)\\.\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/frontend/lib/api/config.ts","lineNumber":106,"sourceCode":"    credentials: 'include',\n    body: JSON.stringify(config),\n  });\n\n  if (!res.ok) {\n    const data = (await res.json().catch(() => ({}))) as { detail?: unknown };\n    // FastAPI returns `detail` as a string OR a structured object (this\n    // endpoint now emits {code, field, missing} for a missing Base URL).\n    // Passing an object straight to `new Error()` renders \"[object Object]\",\n    // so serialize explicitly — same treatment as updateFeaturePrompts.\n    let message: string;\n    if (typeof data.detail === 'string') {\n      message = data.detail;\n    } else if (data.detail) {\n      message = JSON.stringify(data.detail);\n    } else {\n      message = `Failed to update LLM config (status ${res.status}).`;\n    }\n    throw new Error(message);\n  }\n\n  return res.json();\n}\n\n// Legacy function for backwards compatibility\nexport async function updateLlmApiKey(value: string): Promise<string> {\n  const config = await updateLlmConfig({ api_key: value });\n  return config.api_key ?? '';\n}\n\n// Test LLM connection with optional config (for pre-save testing)\nexport async function testLlmConnection(config?: LLMConfigUpdate): Promise<LLMHealthCheck> {\n  const options: RequestInit = {\n    method: 'POST',\n    credentials: 'include',\n  };\n","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/frontend/lib/api/config.ts#L88-L124","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Read the thrown message — a FastAPI `detail` string names the exact invalid field; correct that field in the settings form","Re-login if the status is 401/403 (session expired while the form was open)","Validate the API key/provider/model combination client-side before saving","Check backend logs and its request-validation schema if the message is the generic fallback"],"exampleFix":"// before\nawait updateLlmConfig({ provider: 'openai', model: 'gpt-4', apiKey });\n// after\nif (!apiKey.startsWith('sk-')) { showMsg('Invalid OpenAI API key'); return; }\ntry { await updateLlmConfig(config); }\ncatch (e) { showMsg(e.message); }","handlingStrategy":"validation","validationCode":"function validateLlmConfig(c: LLMConfig): string | null {\n  if (!c.provider) return 'provider required';\n  if (!c.apiKey || c.apiKey.length < 8) return 'apiKey missing/short';\n  if (!c.model) return 'model required';\n  return null;\n}","typeGuard":"function hasDetail(x: unknown): x is { detail: string } {\n  return typeof x === 'object' && x !== null && 'detail' in x && typeof (x as any).detail === 'string';\n}","tryCatchPattern":"try {\n  await updateLlmConfig(cfg);\n} catch (e) {\n  // message already carries backend `detail` when available\n  showToast(e instanceof Error ? e.message : 'Save failed');\n}","preventionTips":["Mirror backend validation rules in the settings form","Re-check auth before PUT/POST mutations after idle periods","Validate provider/model/api-key combinations against a known-good list","Keep frontend config types in sync with the backend schema"],"tags":["http","config","validation","frontend"],"backgroundTag":"http-error-response","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}