diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index 78f739ac..5d7ff70d 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -317,6 +317,10 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account if account == nil { return nil } + schedulable := account.Schedulable + if account.Status == service.StatusError { + schedulable = false + } builder := r.client.Account.UpdateOneID(account.ID). SetName(account.Name). @@ -329,7 +333,7 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account SetPriority(account.Priority). SetStatus(account.Status). SetErrorMessage(account.ErrorMessage). - SetSchedulable(account.Schedulable). + SetSchedulable(schedulable). SetAutoPauseOnExpired(account.AutoPauseOnExpired) if account.RateMultiplier != nil { @@ -716,6 +720,7 @@ func (r *accountRepository) SetError(ctx context.Context, id int64, errorMsg str Where(dbaccount.IDEQ(id)). SetStatus(service.StatusError). SetErrorMessage(errorMsg). + SetSchedulable(false). Save(ctx) if err != nil { return err diff --git a/backend/internal/repository/account_repo_integration_test.go b/backend/internal/repository/account_repo_integration_test.go index d1cea9eb..87ed49ca 100644 --- a/backend/internal/repository/account_repo_integration_test.go +++ b/backend/internal/repository/account_repo_integration_test.go @@ -729,7 +729,7 @@ func (s *AccountRepoSuite) TestUpdateLastUsed() { // --- SetError --- func (s *AccountRepoSuite) TestSetError() { - account := mustCreateAccount(s.T(), s.client, &service.Account{Name: "acc-err", Status: service.StatusActive}) + account := mustCreateAccount(s.T(), s.client, &service.Account{Name: "acc-err", Status: service.StatusActive, Schedulable: true}) s.Require().NoError(s.repo.SetError(s.ctx, account.ID, "something went wrong")) @@ -737,6 +737,22 @@ func (s *AccountRepoSuite) TestSetError() { s.Require().NoError(err) s.Require().Equal(service.StatusError, got.Status) s.Require().Equal("something went wrong", got.ErrorMessage) + s.Require().False(got.Schedulable) +} + +func (s *AccountRepoSuite) TestUpdateErrorStatusUnschedulesAccount() { + account := mustCreateAccount(s.T(), s.client, &service.Account{Name: "acc-update-err", Status: service.StatusActive, Schedulable: true}) + account.Status = service.StatusError + account.ErrorMessage = "token revoked" + account.Schedulable = true + + s.Require().NoError(s.repo.Update(s.ctx, account)) + + got, err := s.repo.GetByID(s.ctx, account.ID) + s.Require().NoError(err) + s.Require().Equal(service.StatusError, got.Status) + s.Require().Equal("token revoked", got.ErrorMessage) + s.Require().False(got.Schedulable) } func (s *AccountRepoSuite) TestClearError_SyncSchedulerSnapshotOnRecovery() { diff --git a/backend/internal/service/token_refresh_service.go b/backend/internal/service/token_refresh_service.go index 22f4aa29..96428847 100644 --- a/backend/internal/service/token_refresh_service.go +++ b/backend/internal/service/token_refresh_service.go @@ -417,11 +417,12 @@ func isNonRetryableRefreshError(err error) bool { } msg := strings.ToLower(err.Error()) nonRetryable := []string{ - "invalid_grant", // refresh_token 已失效 - "invalid_client", // 客户端配置错误 - "unauthorized_client", // 客户端未授权 - "access_denied", // 访问被拒绝 - "missing_project_id", // 缺少 project_id + "invalid_grant", // refresh_token 已失效 + "refresh_token_reused", // OpenAI refresh_token 已被使用,必须重新授权 + "invalid_client", // 客户端配置错误 + "unauthorized_client", // 客户端未授权 + "access_denied", // 访问被拒绝 + "missing_project_id", // 缺少 project_id "no refresh token available", } for _, needle := range nonRetryable { diff --git a/backend/internal/service/token_refresh_service_test.go b/backend/internal/service/token_refresh_service_test.go index 2179a85e..d80b475e 100644 --- a/backend/internal/service/token_refresh_service_test.go +++ b/backend/internal/service/token_refresh_service_test.go @@ -532,6 +532,7 @@ func TestIsNonRetryableRefreshError(t *testing.T) { {name: "network_error", err: errors.New("network timeout"), expected: false}, {name: "invalid_grant", err: errors.New("invalid_grant"), expected: true}, {name: "invalid_client", err: errors.New("invalid_client"), expected: true}, + {name: "refresh_token_reused", err: errors.New(`OPENAI_OAUTH_TOKEN_REFRESH_FAILED: token refresh failed: status 401, body: {"error":{"code":"refresh_token_reused"}}`), expected: true}, {name: "unauthorized_client", err: errors.New("unauthorized_client"), expected: true}, {name: "access_denied", err: errors.New("access_denied"), expected: true}, {name: "no_refresh_token", err: errors.New("no refresh token available"), expected: true},