decolua/9router · error

Model ${modelStr} failed (no fallback)

Error message

Model ${modelStr} failed (no fallback)

What it means

In handleComboChat, a combo model returned a non-2xx response whose status/error text failed checkFallbackError — i.e. the error is classified as non-fallbackable (e.g. 400/401/403/404, invalid request). Rather than burning the remaining combo models, the combo short-circuits: the failing upstream response is returned to the client as-is and 'Model <model> failed (no fallback)' is logged.

Source

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

      } catch {
        // Ignore JSON parse errors
      }

      // Track earliest retryAfter across all combo models
      if (retryAfter && (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter))) {
        earliestRetryAfter = retryAfter;
      }

      // Normalize error text to string (Worker-safe)
      if (typeof errorText !== "string") {
        try { errorText = JSON.stringify(errorText); } catch { errorText = String(errorText); }
      }

      // Check if should fallback to next model
      const { shouldFallback, cooldownMs } = checkFallbackError(result.status, errorText);

      if (!shouldFallback) {
        log.warn("COMBO", `Model ${modelStr} failed (no fallback)`, { status: result.status });
        return result;
      }

      // For transient errors (503/502/504), wait for cooldown before falling through
      // so a briefly-overloaded provider gets a chance to recover rather than being
      // skipped immediately (fixes: combo falls through on transient 503)
      if (cooldownMs && cooldownMs > 0 && cooldownMs <= 5000 &&
          (result.status === 503 || result.status === 502 || result.status === 504)) {
        log.info("COMBO", `Model ${modelStr} transient ${result.status}, waiting ${cooldownMs}ms before next`);
        await new Promise(r => setTimeout(r, cooldownMs));
      }

      // Fallback to next model
      lastError = errorText || String(result.status);
      if (!lastStatus) lastStatus = result.status;
      log.warn("COMBO", `Model ${modelStr} failed, trying next`, { status: result.status });
    } catch (error) {
      // Catch unexpected exceptions to ensure fallback continues

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Inspect the returned response body/status — it is the underlying provider error; fix that cause (key, model id, parameters).
  2. Remove or fix the failing model in the combo definition if it cannot handle your request shape.
  3. Regenerate/rotate the API key or re-OAuth the affected account.
  4. If the error should legitimately fall through (e.g. you want 401 to trigger fallback), adjust checkFallbackError classification in open-sse/services/combo.js.
  5. Normalize request fields so all combo members accept them (drop unsupported params per provider).

Example fix

// before (combo def)
{ name: 'fast', models: ['openai/gpt-4o', 'ollama/llama3'] } // llama3 can't handle tools
// after
{ name: 'fast', models: ['openai/gpt-4o', 'anthropic/claude-sonnet'] } // tool-capable members
Defensive patterns

Strategy: try-catch

Try / catch

const res = await chat({ model: comboName, ... });
if (!res.ok) {
  const err = await res.json().catch(() => ({}));
  // Non-fallbackable: fix request/key for the failing member before retrying
  console.error(`combo member failed terminally: ${res.status} ${err?.error?.message}`);
}

Prevention

When it happens

Trigger: A combo member rejects the request itself: invalid API key (401/403), malformed body or unsupported parameters for that provider (400), unknown model id (404), content-policy refusal. Any request whose error is deterministic — retrying on other models would fail the same way.

Common situations: Combo mixing providers with different request schemas (tools/images not supported by one member); stale API key on one combo model after key rotation; model name typo inside the combo definition; sending Claude-style fields to an OpenAI-only member.

Related errors


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