{"record":{"id":"5446170820463cc4","repo":"decolua/9router","slug":"msg-lasterror-or-all-combo-models-unavailable","errorCode":null,"errorMessage":"${msg} (lastError or \"All combo models unavailable\")","messagePattern":"(.+?) \\(lastError or \"All combo models unavailable\"\\)","errorType":"http","errorClass":null,"httpStatus":503,"severity":"error","filePath":"open-sse/services/combo.js","lineNumber":373,"sourceCode":"    } catch (error) {\n      // Catch unexpected exceptions to ensure fallback continues\n      lastError = error.message || String(error);\n      if (!lastStatus) lastStatus = 500;\n      log.warn(\"COMBO\", `Model ${modelStr} threw error, trying next`, { error: lastError });\n    }\n  }\n\n  // All models failed\n  // Use 503 (Service Unavailable) rather than 406 (Not Acceptable) — 406 implies\n  // the request itself is invalid, but here the providers are simply unavailable\n  // or have no active credentials. 503 is more accurate and retryable by clients.\n  const allDisabled = lastError && lastError.toLowerCase().includes(\"no credentials\");\n  const status = allDisabled ? 503 : (lastStatus || 503);\n  const msg = lastError || \"All combo models unavailable\";\n\n  if (earliestRetryAfter) {\n    const retryHuman = formatRetryAfter(earliestRetryAfter);\n    log.warn(\"COMBO\", `All models failed | ${msg} (${retryHuman})`);\n    return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);\n  }\n\n  log.warn(\"COMBO\", `All models failed | ${msg}`);\n  return new Response(\n    JSON.stringify({ error: { message: msg } }),\n    { status, headers: { \"Content-Type\": \"application/json\" } }\n  );\n}\n\n/**\n * Extract assistant text from a non-stream completion across formats\n * (OpenAI chat, Claude messages, Gemini, OpenAI Responses). Returns \"\" if none.\n * Panel responses are already translated to the client format by chatCore, so the\n * leaf content→string step reuses the translator's own extractTextContent.\n */\nfunction extractPanelText(json) {\n  if (!json || typeof json !== \"object\") return \"\";","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/services/combo.js#L355-L391","documentation":"Every model in the combo failed with fallback-eligible errors. The handler aggregates lastError, the earliest Retry-After across members, and returns unavailableResponse(status, msg, ...) where msg is lastError or the default 'All combo models unavailable'. Status is 503 when the last error mentions 'no credentials', otherwise the first recorded member status or 503. This variant fires when a Retry-After hint exists.","triggerScenarios":"All combo members rate-limited or out of credentials (each returns 429/503 with retryAfter); handleComboChat iterated every rotated model and none returned result.ok; combo members share the same underlying quota (same key, same upstream) so they fail together.","commonSituations":"Combo of aliases all backed by one provider key that hit its rate limit; all member accounts disabled or expired; provider-wide outage during peak hours; the combo configured before member credentials were ever set up.","solutions":["Wait until the Retry-After time in the response before retrying.","Check the specific msg in the response — it identifies which member error came last/first; fix that credential or account.","Add independent credentials (different providers/keys) to the combo so members don't share a quota.","Verify each combo model id is valid and its account is active in the dashboard.","Add client-side exponential backoff instead of immediate retries, which extend the rate-limit lock."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const res = await chat({ model: comboName, ... });\nif (res.status === 503 && res.headers.get('retry-after')) {\n  const waitMs = Number(res.headers.get('retry-after')) * 1000;\n  await new Promise(r => setTimeout(r, waitMs));\n  return retryWithBackoff(chat);\n}","preventionTips":["Ensure combo members use independent credentials so quotas don't fail together.","Honor the returned retryAfter timestamp before any retry.","Monitor per-key quota consumption and pre-warm alternate accounts.","Spread search bursts across time instead of parallel fan-out."],"tags":["combo","http-503","rate-limit","credentials","unavailable"],"backgroundTag":"all-models-unavailable","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}