{"record":{"id":"a47c6b74750a2a93","repo":"decolua/9router","slug":"http-response-status","errorCode":null,"errorMessage":"HTTP ${response.status}","messagePattern":"HTTP \\$\\{response\\.status\\}","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/modelCatalog/sync.js","lineNumber":182,"sourceCode":"    }\n  }\n  return entries;\n}\n\n// Run one sync. Returns a summary, or null when it could not complete.\nexport async function syncModelCatalog() {\n  if (state.running) return null;\n  state.running = true;\n  try {\n    const headers = { accept: \"application/json\" };\n    if (state.etag) headers[\"if-none-match\"] = state.etag;\n    const response = await fetch(CATALOG_URL, { headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n\n    let result;\n    if (response.status === 304) {\n      result = { status: \"unchanged\" };\n    } else if (!response.ok) {\n      throw new Error(`HTTP ${response.status}`);\n    } else {\n      // ~23ms to parse, once a day, on a server that is otherwise idle at this\n      // point — not worth a worker thread.\n      const catalog = await response.json();\n      const etag = response.headers.get(\"etag\") || null;\n      const entries = await collectEntries();\n      const { models, providers } = build(catalog, entries);\n      const serialized = JSON.stringify({ v: 1, etag, syncedAt: Date.now(), models, providers });\n\n      writeAtomic(CATALOG_FILE, serialized);\n      writeAtomic(CATALOG_RAW_FILE, JSON.stringify(slim(catalog)));\n\n      state.etag = etag;\n      invalidateCatalog();\n      result = {\n        status: \"updated\",\n        etag,\n        bytes: Buffer.byteLength(serialized),","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/modelCatalog/sync.js#L164-L200","documentation":"syncModelCatalog fetches the remote model catalog with ETag-based caching. Any non-ok, non-304 HTTP response from the catalog server is rethrown as `HTTP <status>` after the fetch resolves.","triggerScenarios":"The catalog endpoint returns 4xx/5xx: 403/429 (rate limit or blocked), 404 (CATALOG_URL moved), 500/502/503 (server outage). 304 is handled as success (unchanged) and never reaches this throw.","commonSituations":"Catalog host temporarily down or behind a failing CDN; corporate proxy/firewall intercepting and returning an error page; hardcoded CATALOG_URL outdated after the project moved hosting; rate limiting from running many instances behind one IP.","solutions":["Retry later — the sync is periodic and non-fatal; the app keeps working with the previously cached catalog.","Check https://status or the catalog URL manually (curl -I $CATALOG_URL) to see whether the outage is server-side.","If behind a proxy, fix proxy/firewall rules so the request reaches the real host.","Update the app in case CATALOG_URL changed; as a last resort the catalog can be refreshed by a new sync once the server recovers."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    const r = await syncModelCatalog();\n    break;\n  } catch (err) {\n    if (/^HTTP \\d+$/.test(err.message) && attempt < 2) {\n      await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); // backoff, catalog sync is non-fatal\n      continue;\n    }\n    console.warn(\"model catalog sync failed, using cached catalog:\", err.message);\n  }\n}","preventionTips":["Treat catalog sync failures as non-fatal — the cached catalog keeps the app working.","Check the status code in the message: 5xx/429 → retry later; 404 → CATALOG_URL changed, update the app.","If behind a corporate proxy, verify the catalog host is reachable before assuming a server outage."],"tags":["network","http","sync"],"backgroundTag":"http-request-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}