{"record":{"id":"2b3f02a1d870d7d5","repo":"ruvnet/ruflo","slug":"failed-to-fetch-baseurl-models-response-stat-2b3f02","errorCode":null,"errorMessage":"Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText}","messagePattern":"Failed to fetch (.+?)/models: (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/models.ts","lineNumber":325,"sourceCode":"\t\tlogger.info({ baseURL }, \"[models] Using OpenAI-compatible base URL\");\n\n\t\t// Canonical auth token is OPENAI_API_KEY; keep HF_TOKEN as legacy alias\n\t\tconst authToken = config.OPENAI_API_KEY || config.HF_TOKEN;\n\n\t\t// Use auth token from the start if available to avoid rate limiting issues\n\t\t// Some APIs rate-limit unauthenticated requests more aggressively\n\t\tconst response = await fetch(`${baseURL}/models`, {\n\t\t\theaders: authToken ? { Authorization: `Bearer ${authToken}` } : undefined,\n\t\t});\n\t\tlogger.info({ status: response.status }, \"[models] First fetch status\");\n\t\tif (!response.ok && response.status === 401 && !authToken) {\n\t\t\t// If we get 401 and didn't have a token, there's nothing we can do\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText} (no auth token available)`\n\t\t\t);\n\t\t}\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText}`\n\t\t\t);\n\t\t}\n\t\tconst json = await response.json();\n\t\tlogger.info({ keys: Object.keys(json || {}) }, \"[models] Response keys\");\n\n\t\tconst parsed = listSchema.parse(json);\n\t\tlogger.info({ count: parsed.data.length }, \"[models] Parsed models count\");\n\n\t\tlet modelsRaw = parsed.data.map((m) => {\n\t\t\tlet logoUrl: string | undefined = undefined;\n\t\t\tif (isHFRouter && m.id.includes(\"/\")) {\n\t\t\t\tconst org = m.id.split(\"/\")[0];\n\t\t\t\tlogoUrl = `https://huggingface.co/api/avatars/${encodeURIComponent(org)}`;\n\t\t\t}\n\n\t\t\tconst inputModalities = (m.architecture?.input_modalities ?? []).map((modality) =>\n\t\t\t\tmodality.toLowerCase()","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/models.ts#L307-L343","documentation":"Generic non-OK response from the upstream model-list fetch in buildModels(). Covers every failure other than the '401-without-token' case: 401 with a token that was rejected, 403, 404, 429, and 5xx. The message embeds the status code and status text so the operator can see exactly what the gateway replied.","triggerScenarios":"GET ${OPENAI_BASE_URL}/models returns a non-2xx status: 401/403 with an invalid or expired token; 404 from a base URL missing or adding the /v1 suffix; 429 from rate limiting; 500-503 during provider incidents.","commonSituations":"Wrong base URL path (https://host instead of https://host/v1, or doubling /v1); expired HF token; gateway rate limits unauthenticated or bursty clients; temporary upstream outage during a scheduled refresh.","solutions":["Reproduce the exact request: curl -i -H \"Authorization: Bearer $OPENAI_API_KEY\" \"$OPENAI_BASE_URL/models\" and match the status code.","404: fix the base URL — most OpenAI-compatible servers expose /models only under the /v1 prefix (or already include it, don't add it twice).","401/403: rotate or re-issue the API token and confirm it belongs to the same provider as the base URL.","429: wait or reduce refresh frequency; the periodic model refresh will succeed later and the previous cache keeps serving.","5xx: check the provider status page and retry after the incident."],"exampleFix":"# before (404 — missing /v1)\nOPENAI_BASE_URL=https://router.huggingface.co\n\n# after\nOPENAI_BASE_URL=https://router.huggingface.co/v1","handlingStrategy":"retry","validationCode":"const url = `${OPENAI_BASE_URL}/models`; // sanity-check shape before fetching\nnew URL(url); // throws on malformed base\nif (!/\\/v1\\/?$/.test(url)) logger.warn(\"base URL may be missing the /v1 prefix\");","typeGuard":null,"tryCatchPattern":"for (let attempt = 1; attempt <= 3; attempt++) {\n\ttry {\n\t\tawait buildModels();\n\t\tbreak;\n\t} catch (err) {\n\t\tconst msg = String(err);\n\t\tif (msg.includes(\" 429 \") || /\\b5\\d\\d\\b/.test(msg)) {\n\t\t\tawait delay(2 ** attempt * 1000); // transient — retry with backoff\n\t\t\tcontinue;\n\t\t}\n\t\tthrow err; // 401/403/404 are permanent — fix config instead\n\t}\n}","preventionTips":["Validate the base URL and token with a preflight curl before boot.","Retry only transient statuses (429, 5xx) with exponential backoff; fail fast on 401/403/404.","Cache the last good model list so transient upstream failures don't degrade service."],"tags":["http","network","models","upstream"],"backgroundTag":"http-request-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}