From 1f6a73f0db3b1f16acdf5da20e0f40b5bcfe6a0f Mon Sep 17 00:00:00 2001 From: QTom Date: Thu, 2 Apr 2026 20:44:05 +0800 Subject: [PATCH] fix(openai): treat 401 {"detail":"Unauthorized"} as permanent auth failure - ratelimit_service: detect non-standard OpenAI 401 format and permanently disable account - account_test_service: mark account error on 401 during connection test Made-with: Cursor --- backend/internal/service/account_test_service.go | 5 +++++ backend/internal/service/ratelimit_service.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go index fec98e12..8218c2db 100644 --- a/backend/internal/service/account_test_service.go +++ b/backend/internal/service/account_test_service.go @@ -551,6 +551,11 @@ func (s *AccountTestService) testOpenAIAccountConnection(c *gin.Context, account account.RateLimitResetAt = resetAt } } + // 401 Unauthorized: 标记账号为永久错误 + if resp.StatusCode == http.StatusUnauthorized && s.accountRepo != nil { + errMsg := fmt.Sprintf("Authentication failed (401): %s", string(body)) + _ = s.accountRepo.SetError(ctx, account.ID, errMsg) + } return s.sendErrorAndEnd(c, fmt.Sprintf("API returned %d: %s", resp.StatusCode, string(body))) } diff --git a/backend/internal/service/ratelimit_service.go b/backend/internal/service/ratelimit_service.go index aa0ae200..4f5b57cc 100644 --- a/backend/internal/service/ratelimit_service.go +++ b/backend/internal/service/ratelimit_service.go @@ -161,6 +161,16 @@ func (s *RateLimitService) HandleUpstreamError(ctx context.Context, account *Acc shouldDisable = true break } + // OpenAI: {"detail":"Unauthorized"} 表示 token 完全无效(非标准 OpenAI 错误格式),直接标记 error + if account.Platform == PlatformOpenAI && gjson.GetBytes(responseBody, "detail").String() == "Unauthorized" { + msg := "Unauthorized (401): account authentication failed permanently" + if upstreamMsg != "" { + msg = "Unauthorized (401): " + upstreamMsg + } + s.handleAuthError(ctx, account, msg) + shouldDisable = true + break + } // OAuth 账号在 401 错误时临时不可调度(给 token 刷新窗口);非 OAuth 账号保持原有 SetError 行为。 // Antigravity 除外:其 401 由 applyErrorPolicy 的 temp_unschedulable_rules 自行控制。 if account.Type == AccountTypeOAuth && account.Platform != PlatformAntigravity {