{"record":{"id":"f532341fc573d365","repo":"janhq/jan","slug":"failed-to-fetch-model-catalog-response-status","errorCode":null,"errorMessage":"Failed to fetch model catalog: ${response.status} ${response.statusText}","messagePattern":"Failed to fetch model catalog: (.+?) (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/services/models/default.ts","lineNumber":54,"sourceCode":"export class DefaultModelsService implements ModelsService {\n  private getEngine(provider: string = defaultProvider) {\n    return EngineManager.instance().get(provider) as AIEngine | undefined\n  }\n\n  async getModel(modelId: string): Promise<modelInfo | undefined> {\n    return this.getEngine()?.get(modelId)\n  }\n\n  async fetchModels(): Promise<modelInfo[]> {\n    return this.getEngine()?.list() ?? []\n  }\n\n  async fetchModelCatalog(): Promise<ModelCatalog> {\n    try {\n      const response = await fetch(MODEL_CATALOG_URL)\n\n      if (!response.ok) {\n        throw new Error(\n          `Failed to fetch model catalog: ${response.status} ${response.statusText}`\n        )\n      }\n\n      const catalog: ModelCatalog = await response.json()\n      return catalog\n    } catch (error) {\n      console.error('Error fetching model catalog:', error)\n      throw new Error(\n        `Failed to fetch model catalog: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async fetchLatestJanModel(): Promise<CatalogModel | null> {\n    try {\n      const response = await fetch(LATEST_JAN_MODEL_URL)\n","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/web-app/src/services/models/default.ts#L36-L72","documentation":"Thrown by ModelService.fetchModelCatalog (default.ts:54) when the HTTP response from MODEL_CATALOG_URL is not OK (response.ok is false). The status and statusText are embedded in the message. This is the inner 'response not ok' branch before JSON parsing; the surrounding catch re-wraps it as error [154].","triggerScenarios":"The remote model catalog endpoint returns a non-2xx status: 404 (catalog URL moved), 500/502/503 (catalog server down), 429 (rate-limited), or a CDN error.","commonSituations":"Catalog hosting outage; the hardcoded MODEL_CATALOG_URL changed in a new release but the client is old; corporate firewall returns a blocking page with a non-2xx status; transient CDN blip.","solutions":["Retry — most catalog failures are transient (5xx / CDN).","Update the app so MODEL_CATALOG_URL points at the current catalog host.","Check network/proxy: ensure the catalog host is reachable and not blocked by a captive portal or firewall.","Fall back to the cached catalog if available rather than failing the whole Hub view."],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)\n}\n// after: retry once on transient errors, then degrade to cached catalog\nif (!response.ok) {\n  if (response.status >= 500 && attempt < 2) { await delay(500); continue }\n  return getCachedCatalog() ?? throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)\n}","handlingStrategy":"retry","validationCode":"async function isCatalogReachable(url: string): Promise<boolean> {\n  try {\n    const r = await fetch(url, { method: 'HEAD' })\n    return r.ok || r.status === 405  // some hosts reject HEAD\n  } catch { return false }\n}","typeGuard":"function isOkResponse(r: Response): boolean {\n  return r.ok\n}","tryCatchPattern":"for (let attempt = 0; attempt < 2; attempt++) {\n  try {\n    const response = await fetch(MODEL_CATALOG_URL)\n    if (response.ok) return await response.json()\n    if (response.status < 500) throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)\n    await new Promise(r => setTimeout(r, 500))\n  } catch (e) { if (attempt === 1) throw e }\n}","preventionTips":["Retry transient (5xx) catalog failures once before surfacing.","Cache the last successful catalog for offline / degraded mode.","Keep MODEL_CATALOG_URL current across releases.","Distinguish status errors from network errors in the surfaced message."],"tags":["catalog","network","http-status","fetch","typescript"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}