From 5e1fe650276e5e7b4f6ddb2128704f6eba1d921d Mon Sep 17 00:00:00 2001 From: merlinmiao <820962493@qq.com> Date: Sun, 5 Apr 2026 18:13:51 +0800 Subject: [PATCH] fix(provider): classify HTTP 404 as retriable to trigger model fallback HTTP 404 errors (e.g., "No endpoints found for model" from OpenRouter) were previously unclassified, causing FallbackChain.Execute to return "unclassified error" immediately without trying fallback candidates. This change adds 404 to classifyByStatus() returning FailoverTimeout, which is retriable, so the fallback chain proceeds to the next model. Fixes #2334 Co-Authored-By: Claude Opus 4.6 --- pkg/providers/error_classifier.go | 2 ++ pkg/providers/error_classifier_test.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) 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")