From 8db8791a73aebc2afa60e6c61534894028d8b219 Mon Sep 17 00:00:00 2001 From: IanShaw <131567472+IanShaw027@users.noreply.github.com> Date: Thu, 20 Aug 2026 05:53:55 -0700 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=20Grok=20=E6=99=AE=E9=80=9A=20429=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9C=89=E9=99=90=E5=90=8C=E5=8F=B7=E9=87=8D?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/service/grok_upstream_failure.go | 9 ++++++++- backend/internal/service/grok_upstream_failure_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/internal/service/grok_upstream_failure.go b/backend/internal/service/grok_upstream_failure.go index 3d6aa05c6..51edbc333 100644 --- a/backend/internal/service/grok_upstream_failure.go +++ b/backend/internal/service/grok_upstream_failure.go @@ -402,6 +402,13 @@ func grokRetryableOnSameAccount(account *Account, statusCode int, responseBody [ if statusCode == http.StatusTooManyRequests { return true } + case GrokFailureRateLimit: + // A transient 429 does not identify a bad credential. Give every Grok + // account a bounded same-account retry window before failover; the + // failover loop still caps attempts and the client receives 429 after it. + if statusCode == http.StatusTooManyRequests { + return true + } } return account.IsPoolMode() && account.IsPoolModeRetryableStatus(statusCode) } @@ -411,7 +418,7 @@ func grokSameAccountRetryMetadata(account *Account, statusCode int, responseBody return false, 0, time.Time{} } decision := classifyGrokUpstreamFailure(statusCode, responseBody, "") - if decision.Class != GrokFailureModelCapacity { + if decision.Class != GrokFailureModelCapacity && decision.Class != GrokFailureRateLimit { return true, 0, time.Time{} } return true, 500 * time.Millisecond, time.Now().Add(30 * time.Second) diff --git a/backend/internal/service/grok_upstream_failure_test.go b/backend/internal/service/grok_upstream_failure_test.go index d6e6ec7ac..0cc8cd1ef 100644 --- a/backend/internal/service/grok_upstream_failure_test.go +++ b/backend/internal/service/grok_upstream_failure_test.go @@ -81,7 +81,7 @@ func TestGrokRetryableOnSameAccount_CapacityAndRateLimit(t *testing.T) { account := &Account{ID: 9105, Platform: PlatformGrok, Type: AccountTypeOAuth} require.True(t, grokRetryableOnSameAccount(account, http.StatusTooManyRequests, []byte(`{"error":{"message":"The model is currently at capacity due to high demand"}}`))) - require.False(t, grokRetryableOnSameAccount(account, http.StatusTooManyRequests, + require.True(t, grokRetryableOnSameAccount(account, http.StatusTooManyRequests, []byte(`{"error":{"message":"rate limit exceeded"}}`))) require.False(t, grokRetryableOnSameAccount(account, http.StatusPaymentRequired, []byte(`{"error":{"message":"You have run out of credits or need a Grok subscription"}}`))) @@ -118,9 +118,9 @@ func TestGrokSameAccountRetryMetadata_CapacityDeadline(t *testing.T) { retryable, delay, deadline = grokSameAccountRetryMetadata(account, http.StatusTooManyRequests, []byte(`{"error":{"message":"rate limit exceeded"}}`)) - require.False(t, retryable) - require.Zero(t, delay) - require.True(t, deadline.IsZero()) + require.True(t, retryable) + require.Equal(t, 500*time.Millisecond, delay) + require.WithinDuration(t, time.Now().Add(30*time.Second), deadline, 2*time.Second) } func TestClassifyGrokUpstreamFailure_ValidationNoCool(t *testing.T) {