diff --git a/pkg/providers/error_classifier.go b/pkg/providers/error_classifier.go index e7691aa93..18fb9e6ab 100644 --- a/pkg/providers/error_classifier.go +++ b/pkg/providers/error_classifier.go @@ -177,6 +177,8 @@ func classifyByStatus(status int) FailoverReason { return FailoverAuth case status == 402: return FailoverBilling + case status == 404: + return FailoverTimeout case status == 408: return FailoverTimeout case status == 429: diff --git a/pkg/providers/error_classifier_test.go b/pkg/providers/error_classifier_test.go index 46b180835..6c8097847 100644 --- a/pkg/providers/error_classifier_test.go +++ b/pkg/providers/error_classifier_test.go @@ -39,6 +39,7 @@ func TestClassifyError_StatusCodes(t *testing.T) { {401, FailoverAuth}, {403, FailoverAuth}, {402, FailoverBilling}, + {404, FailoverTimeout}, {408, FailoverTimeout}, {429, FailoverRateLimit}, {400, FailoverFormat}, @@ -264,6 +265,22 @@ func TestClassifyError_UnknownError(t *testing.T) { } } +func TestClassifyError_404TriggersFallback(t *testing.T) { + // OpenRouter returns 404 when model endpoint doesn't exist. + // This should be classified as FailoverTimeout (retriable) so fallback kicks in. + err := fmt.Errorf("fallback: unclassified error from openrouter/qwen3.6-plus-preview:free: API request failed:\n Status: 404\n Body: {\"error\":{\"message\":\"No endpoints found for qwen3.6-plus-preview:free.\",\"code\":404}}") + result := ClassifyError(err, "openrouter", "qwen3.6-plus-preview:free") + if result == nil { + t.Fatal("expected non-nil for 404 error") + } + if result.Reason != FailoverTimeout { + t.Errorf("reason = %q, want %q (retriable so fallback is triggered)", result.Reason, FailoverTimeout) + } + if !result.IsRetriable() { + t.Errorf("expected 404 to be retriable") + } +} + func TestClassifyError_ProviderModelPropagation(t *testing.T) { err := errors.New("rate limit exceeded") result := ClassifyError(err, "my-provider", "my-model")