{"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":"http","errorClass":"Error","httpStatus":null,"severity":"critical","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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/ruflo/src/ruvocal/src/lib/server/models.ts#L307-L343","documentation":"Thrown by buildModels when GET ${baseURL}/models returns any non-ok status that is not the '401 with no token' case. This is the catch-all for 403 Forbidden, 404 Not Found, 429 Too Many Requests, 5xx server errors, and other HTTP failures. The message includes the status code, status text, and base URL so the operator can localize the problem.","triggerScenarios":"OPENAI_BASE_URL points at a URL whose /models path returns 403 (forbidden/wrong key scope), 404 (wrong base path), 429 (rate limited at boot), 500/502/503 (upstream incident), or a redirect that resolves to a non-ok response. Also thrown when a valid token is present but the gateway still rejects (e.g. 403 with a wrong-scope token).","commonSituations":"Wrong base URL (missing /v1 suffix, typo, pointing at a non-OpenAI API); token with insufficient scope (403); gateway outage (5xx); aggressive boot-time rate limiting (429) when many instances start at once; reverse proxy returning 502 because the upstream is down; base URL with a trailing slash or path that 404s on /models.","solutions":["curl -i -H \"Authorization: Bearer $OPENAI_API_KEY\" $OPENAI_BASE_URL/models to reproduce and inspect the response.","For 404: verify the base URL ends with the OpenAI-compatible path (commonly /v1) and that /models exists.","For 403: confirm the token has the required scope/entitlement for the gateway.","For 429/5xx: wait and retry; if recurring, contact the gateway provider or raise limits.","Strip trailing slashes from OPENAI_BASE_URL (the code already strips one trailing slash, but verify the URL shape)."],"exampleFix":"# before\nOPENAI_BASE_URL=https://router.huggingface.co\n\n# after (add /v1)\nOPENAI_BASE_URL=https://router.huggingface.co/v1","handlingStrategy":"retry","validationCode":"async function probeModelsEndpoint(baseURL: string, token?: string) {\n  const r = await fetch(`${baseURL.replace(/\\/$/, \"\")}/models`, {\n    headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n  });\n  return { ok: r.ok, status: r.status, statusText: r.statusText };\n}","typeGuard":"function isModelsFetchFailure(e: unknown, statusRe?: RegExp): e is Error {\n  if (!(e instanceof Error)) return false;\n  if (!/Failed to fetch .*\\/models:/.test(e.message)) return false;\n  return statusRe ? statusRe.test(e.message) : true;\n}","tryCatchPattern":"try { await rebuildModels(); }\ncatch (e) {\n  if (e instanceof Error && /Failed to fetch .*\\/models/.test(e.message)) {\n    const m = /:(\\d{3})\\s/.exec(e.message);\n    const status = m ? Number(m[1]) : 0;\n    if (status === 429 || status >= 500) {\n      await new Promise((r) => setTimeout(r, 5000));\n      return rebuildModels(); // one retry\n    }\n  }\n  throw e;\n}","preventionTips":["curl -i the /models endpoint with the configured token before starting the server.","Ensure OPENAI_BASE_URL ends with the OpenAI-compatible path (commonly /v1).","Confirm the token has the scope/entitlement required by the gateway (avoid 403).","For multi-instance deploys, stagger boot to avoid 429 at startup."],"tags":["models","network","http","config","startup","openai"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}