{"record":{"id":"885d69eb19c1dbfe","repo":"janhq/jan","slug":"failed-to-fetch-models-from-provider-provider-885d69","errorCode":null,"errorMessage":"Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}","messagePattern":"Failed to fetch models from (.+?): (.+?) (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/services/providers/tauri.ts","lineNumber":217,"sourceCode":"        }\n\n        if (!response.ok) {\n          if (response.status === 401) {\n            throw new Error(\n              `Authentication failed: API key is required or invalid for ${provider.provider}`\n            )\n          }\n          if (response.status === 403) {\n            throw new Error(\n              `Access forbidden: Check your API key permissions for ${provider.provider}`\n            )\n          }\n          if (response.status === 404) {\n            throw new Error(\n              `Models endpoint not found for ${provider.provider}. Check the base URL configuration.`\n            )\n          }\n          throw new Error(\n            `Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`\n          )\n        }\n\n        const data = await response.json()\n\n        if (data.data && Array.isArray(data.data)) {\n          return data.data\n            .map((model: { id: string }) => model.id)\n            .filter(Boolean)\n        }\n        if (Array.isArray(data)) {\n          return data\n            .filter(Boolean)\n            .map((model) =>\n              typeof model === 'object' && 'id' in model ? model.id : model\n            )\n        }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/web-app/src/services/providers/tauri.ts#L199-L235","documentation":"Thrown by getModels when the provider's GET {base_url}/models returns a non-OK HTTP status that is not one of the specifically handled codes (401/403/404). It surfaces the raw status code and status text so the caller can diagnose provider-side problems (5xx outages, 429 rate limits, 400 bad requests, 405 method not allowed, etc.). It is the catch-all for any !response.ok branch the method does not recognize.","triggerScenarios":"The fetchTauri call to ${provider.base_url}/models resolved with response.ok === false AND response.status is not 401, 403, or 404. Concretely: 500/502/503 server errors, 429 on the final key attempt (the continue branch is skipped when ki === keyAttempts.length-1), 400 Bad Request, 405 Method Not Allowed.","commonSituations":"Provider service is up but erroring (5xx); rate-limited after exhausting the whole API-key chain (429); base_url points at a valid host but wrong path returning 400; provider does not implement an OpenAI-compatible GET /models; temporary provider outage.","solutions":["Read the numeric status embedded in the message: 5xx -> provider is down, retry later / check the provider status page; 429 -> rate limit, reduce frequency or use a key with higher quota; 4xx other than auth -> verify base_url path and that the provider implements /models.","Reproduce with curl: curl -i -H \"x-api-key: $KEY\" -H \"Authorization: Bearer $KEY\" ${base_url}/models to see the raw response.","Verify provider configuration (base_url, custom_header entries) in provider settings.","If 429 persists across all keys, back off, rotate keys, or switch providers."],"exampleFix":"// before: every non-ok non-401/403/404 falls into one generic throw\nif (!response.ok) {\n  if (response.status === 404) { /* ... */ }\n  throw new Error(`Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`)\n}\n// after: branch transient 5xx / 429 for caller retry guidance\nif (!response.ok) {\n  if (response.status >= 500 || response.status === 429) {\n    throw new Error(`${provider.provider} is temporarily unavailable (${response.status}). Retry shortly.`)\n  }\n  throw new Error(`Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`)\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight the endpoint before listing models\nasync function isModelsEndpointOk(base_url: string, key?: string): Promise<boolean> {\n  const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n  if (key) { headers['x-api-key'] = key; headers['Authorization'] = `Bearer ${key}` }\n  try {\n    const res = await fetch(`${base_url}/models`, { method: 'GET', headers })\n    return res.ok\n  } catch { return false }\n}","typeGuard":"function isNonOkStatus(status: number): boolean {\n  return !(status >= 200 && status < 300)\n}","tryCatchPattern":"try {\n  const models = await provider.getModels(providerConfig)\n} catch (e) {\n  const msg = e instanceof Error ? e.message : ''\n  const m = msg.match(/:\\s(\\d{3})\\s/)\n  if (m) {\n    const status = Number(m[1])\n    if (status >= 500 || status === 429) scheduleRetry()\n    else showUserError(msg)\n  } else throw e\n}","preventionTips":["Validate base_url reachability before saving provider settings.","Handle 5xx/429 with retry and exponential backoff in the UI layer.","Cache the last successful model list so transient provider errors don't blank the UI."],"tags":["network","http","provider-api","models","diagnostic"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}