decolua/9router · error

${msg} (lastError or "All combo models unavailable")

Error message

${msg} (lastError or "All combo models unavailable")

What it means

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.

Source

Thrown at open-sse/services/combo.js:373

    } catch (error) {
      // Catch unexpected exceptions to ensure fallback continues
      lastError = error.message || String(error);
      if (!lastStatus) lastStatus = 500;
      log.warn("COMBO", `Model ${modelStr} threw error, trying next`, { error: lastError });
    }
  }

  // All models failed
  // Use 503 (Service Unavailable) rather than 406 (Not Acceptable) — 406 implies
  // the request itself is invalid, but here the providers are simply unavailable
  // or have no active credentials. 503 is more accurate and retryable by clients.
  const allDisabled = lastError && lastError.toLowerCase().includes("no credentials");
  const status = allDisabled ? 503 : (lastStatus || 503);
  const msg = lastError || "All combo models unavailable";

  if (earliestRetryAfter) {
    const retryHuman = formatRetryAfter(earliestRetryAfter);
    log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
    return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);
  }

  log.warn("COMBO", `All models failed | ${msg}`);
  return new Response(
    JSON.stringify({ error: { message: msg } }),
    { status, headers: { "Content-Type": "application/json" } }
  );
}

/**
 * Extract assistant text from a non-stream completion across formats
 * (OpenAI chat, Claude messages, Gemini, OpenAI Responses). Returns "" if none.
 * Panel responses are already translated to the client format by chatCore, so the
 * leaf content→string step reuses the translator's own extractTextContent.
 */
function extractPanelText(json) {
  if (!json || typeof json !== "object") return "";

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Wait until the Retry-After time in the response before retrying.
  2. Check the specific msg in the response — it identifies which member error came last/first; fix that credential or account.
  3. Add independent credentials (different providers/keys) to the combo so members don't share a quota.
  4. Verify each combo model id is valid and its account is active in the dashboard.
  5. Add client-side exponential backoff instead of immediate retries, which extend the rate-limit lock.
Defensive patterns

Strategy: retry

Try / catch

const res = await chat({ model: comboName, ... });
if (res.status === 503 && res.headers.get('retry-after')) {
  const waitMs = Number(res.headers.get('retry-after')) * 1000;
  await new Promise(r => setTimeout(r, waitMs));
  return retryWithBackoff(chat);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/5446170820463cc4. Report an issue: GitHub.