diff --git a/backend/internal/service/grok_p2_test.go b/backend/internal/service/grok_p2_test.go index d3d6e67cf6..a110e15875 100644 --- a/backend/internal/service/grok_p2_test.go +++ b/backend/internal/service/grok_p2_test.go @@ -93,9 +93,9 @@ func TestApplyGrokUpstreamFailure_ModelSpecificFreeUsage(t *testing.T) { svc.handleGrokAccountUpstreamError(context.Background(), account, 400, nil, body) - require.Equal(t, 1, repo.tempUnschedCalls) - require.Equal(t, "grok free usage exhausted", repo.lastTempUnschedReason) + require.Zero(t, repo.tempUnschedCalls, "model-scoped free usage must not cool sibling models") require.True(t, isGrokModelQuotaBlocked(account.ID, "grok-4.5", time.Now())) + require.False(t, isGrokModelQuotaBlocked(account.ID, "grok-4.3", time.Now())) } func TestApplyGrokUpstreamFailure_SpendingLimitMarksReauth(t *testing.T) { diff --git a/backend/internal/service/grok_upstream_failure.go b/backend/internal/service/grok_upstream_failure.go index 0979e5edb3..6dba876508 100644 --- a/backend/internal/service/grok_upstream_failure.go +++ b/backend/internal/service/grok_upstream_failure.go @@ -480,11 +480,10 @@ func (s *OpenAIGatewayService) applyGrokUpstreamFailureDecision( if decision.Model != "" && isGrokModelSpecificFreeUsage(low, decision.Model) { until := time.Now().Add(decision.Cooldown) markGrokModelQuotaBlock(account.ID, decision.Model, until) - // Account-wide cool is still applied as a safety net when the body - // does not isolate a model cleanly; for model-specific free we only - // model-block and skip the long account cool when BlockModel was set - // by the classifier... actually free-usage keeps BlockModel false. - // Prefer model block + shorter account cool via tempUnschedule below. + // The upstream explicitly scoped exhaustion to this model, so an + // account-wide cool would incorrectly take healthy sibling models out + // of rotation. + return true } coolUntil := time.Now().Add(decision.Cooldown) markGrokFreeUsageRecovery(account.ID, coolUntil)