feat(auth): add explicit handling for count_tokens endpoint errors and enhance model availability logic

- Implemented logic to differentiate between generic and explicit model-not-found errors for the `count_tokens` endpoint.
- Introduced `recordAvailabilityNeutralResult` to ensure models remain active for generic endpoint errors.
- Expanded test coverage to verify behavior for various 404 scenarios, including error propagation and model suspension handling.
- Refactored error-parsing utilities to identify structured and nested model-not-found errors.

Closes: #4410
This commit is contained in:
Luis Pater
2026-07-19 03:39:27 +08:00
parent 0b2ce80fcb
commit 36ed0ca5ce
2 changed files with 630 additions and 6 deletions

View File

@@ -2733,7 +2733,15 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
if ra := retryAfterFromError(errExec); ra != nil {
result.RetryAfter = ra
}
m.MarkResult(execCtx, result)
// Some Anthropic-compatible upstreams do not implement the
// count_tokens route and return a generic endpoint 404. Record
// the failure for hooks and metrics without suspending a model
// that remains usable through the messages endpoint.
if isCountTokensEndpointNotFoundError(errExec, execReq.Model) {
m.recordAvailabilityNeutralResult(execCtx, result)
} else {
m.MarkResult(execCtx, result)
}
if isRequestInvalidError(errExec) {
return cliproxyexecutor.Response{}, errExec
}
@@ -3891,6 +3899,30 @@ func (m *Manager) MarkResult(ctx context.Context, result Result) {
m.publishErrorEvent(result, authSnapshot)
}
func (m *Manager) recordAvailabilityNeutralResult(ctx context.Context, result Result) {
if result.AuthID == "" {
return
}
var authSnapshot *Auth
m.mu.Lock()
if auth, ok := m.auths[result.AuthID]; ok && auth != nil {
now := time.Now()
auth.recordRecentRequest(now, result.Success)
if result.Success {
auth.Success++
} else {
auth.Failed++
}
_ = m.persist(ctx, auth)
authSnapshot = auth.Clone()
}
m.mu.Unlock()
m.hook.OnResult(ctx, result)
m.publishErrorEvent(result, authSnapshot)
}
func ensureModelState(auth *Auth, model string) *ModelState {
if auth == nil || model == "" {
return nil
@@ -4096,9 +4128,15 @@ func resultErrorFromError(err error) *Error {
if err == nil {
return nil
}
resultErr := &Error{
Message: err.Error(),
HTTPStatus: statusCodeFromError(err),
var sourceErr *Error
var resultErr *Error
if errors.As(err, &sourceErr) && sourceErr != nil {
resultErr = cloneError(sourceErr)
} else {
resultErr = &Error{Message: err.Error()}
}
if resultErr.HTTPStatus == 0 {
resultErr.HTTPStatus = statusCodeFromError(err)
}
if isRequestScopedError(err) || isRequestInvalidError(err) {
resultErr.Code = requestScopedErrorCode
@@ -4296,6 +4334,199 @@ func isRequestScopedResultError(err *Error) bool {
return err != nil && (err.IsRequestScoped() || isRequestScopedNotFoundResultError(err))
}
func isCountTokensEndpointNotFoundError(err error, requestedModel string) bool {
if err == nil || statusCodeFromError(err) != http.StatusNotFound {
return false
}
baseModel := thinking.ParseSuffix(requestedModel).ModelName
return !isExplicitModelNotFoundError(err, baseModel)
}
func isExplicitModelNotFoundError(err error, requestedModel string) bool {
if err == nil {
return false
}
if authErr, ok := err.(*Error); ok && authErr != nil {
if isModelNotFoundIdentifier(authErr.Code) || isStructuredModelNotFoundError(authErr.Message, requestedModel) {
return true
}
} else if isStructuredModelNotFoundError(err.Error(), requestedModel) {
return true
}
switch wrapped := err.(type) {
case interface{ Unwrap() []error }:
for _, nested := range wrapped.Unwrap() {
if isExplicitModelNotFoundError(nested, requestedModel) {
return true
}
}
case interface{ Unwrap() error }:
return isExplicitModelNotFoundError(wrapped.Unwrap(), requestedModel)
}
return false
}
func isStructuredModelNotFoundError(message, requestedModel string) bool {
var payload any
if errJSON := json.Unmarshal([]byte(strings.TrimSpace(message)), &payload); errJSON != nil {
return false
}
return containsStructuredModelNotFound(payload, requestedModel)
}
func containsStructuredModelNotFound(value any, requestedModel string) bool {
switch typed := value.(type) {
case map[string]any:
notFoundType := false
exactModelReference := false
for key, item := range typed {
text, isString := item.(string)
if isString {
switch strings.ToLower(strings.TrimSpace(key)) {
case "code":
if isModelNotFoundIdentifier(text) {
return true
}
case "type":
if isModelNotFoundIdentifier(text) {
return true
}
notFoundType = notFoundType || isNotFoundErrorIdentifier(text)
case "error", "message", "detail", "error_description", "title":
if isExplicitModelNotFoundMessage(text, requestedModel) {
return true
}
exactModelReference = exactModelReference || isExactRequestedModelReference(text, requestedModel)
}
}
switch item.(type) {
case map[string]any, []any:
if containsStructuredModelNotFound(item, requestedModel) {
return true
}
}
}
return notFoundType && exactModelReference
case []any:
for _, item := range typed {
if text, isString := item.(string); isString && isExplicitModelNotFoundMessage(text, requestedModel) {
return true
}
if containsStructuredModelNotFound(item, requestedModel) {
return true
}
}
}
return false
}
func isModelNotFoundIdentifier(value string) bool {
candidate := strings.ToLower(strings.TrimSpace(value))
if fragment := strings.LastIndex(candidate, "#"); fragment >= 0 && fragment+1 < len(candidate) {
candidate = candidate[fragment+1:]
} else {
if query := strings.Index(candidate, "?"); query >= 0 {
candidate = candidate[:query]
}
candidate = strings.TrimRight(candidate, "/")
if separator := strings.LastIndexAny(candidate, "/:"); separator >= 0 {
candidate = candidate[separator+1:]
}
}
normalized := strings.NewReplacer("-", "_", " ", "_").Replace(candidate)
switch normalized {
case "model_not_found", "model_not_found_error", "unknown_model", "model_does_not_exist", "model_not_exist":
return true
default:
return false
}
}
func isNotFoundErrorIdentifier(value string) bool {
normalized := strings.NewReplacer("-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
return normalized == "not_found" || normalized == "not_found_error"
}
func isExplicitModelNotFoundMessage(message, requestedModel string) bool {
lower := strings.Trim(strings.ToLower(strings.TrimSpace(message)), " .!;\t\r\n")
if lower == "" {
return false
}
normalized := strings.NewReplacer("-", "_", " ", "_").Replace(lower)
if strings.Contains(normalized, "model_not_found") || strings.Contains(normalized, "unknown_model") {
return true
}
for _, prefix := range []string{"no such model", "unknown model"} {
if lower != prefix && !strings.HasPrefix(lower, prefix+" ") && !strings.HasPrefix(lower, prefix+":") {
continue
}
remainder := strings.TrimSpace(strings.TrimPrefix(lower, prefix))
remainder = strings.TrimSpace(strings.TrimPrefix(remainder, ":"))
if remainder == "" {
return true
}
missingSuffix, matches := trimRequestedModelReference(remainder, requestedModel)
return matches && missingSuffix == ""
}
for _, prefix := range []string{"the requested model", "requested model", "the model", "model"} {
if lower != prefix && !strings.HasPrefix(lower, prefix+" ") && !strings.HasPrefix(lower, prefix+":") {
continue
}
remainder := strings.TrimSpace(strings.TrimPrefix(lower, prefix))
remainder = strings.TrimSpace(strings.TrimPrefix(remainder, ":"))
if isMissingModelPhrase(remainder) {
return true
}
missingSuffix, matches := trimRequestedModelReference(remainder, requestedModel)
return matches && isMissingModelPhrase(missingSuffix)
}
return false
}
func isExactRequestedModelReference(message, requestedModel string) bool {
lower := strings.Trim(strings.ToLower(strings.TrimSpace(message)), " .!;\t\r\n")
for _, prefix := range []string{"the requested model", "requested model", "the model", "model"} {
if lower != prefix && !strings.HasPrefix(lower, prefix+" ") && !strings.HasPrefix(lower, prefix+":") {
continue
}
remainder := strings.TrimSpace(strings.TrimPrefix(lower, prefix))
remainder = strings.TrimSpace(strings.TrimPrefix(remainder, ":"))
suffix, matches := trimRequestedModelReference(remainder, requestedModel)
return matches && suffix == ""
}
return false
}
func trimRequestedModelReference(value, requestedModel string) (string, bool) {
model := strings.ToLower(strings.TrimSpace(requestedModel))
if model == "" {
return "", false
}
for _, candidate := range []string{model, "'" + model + "'", `"` + model + `"`, "`" + model + "`"} {
if value == candidate {
return "", true
}
if !strings.HasPrefix(value, candidate) {
continue
}
remainder := value[len(candidate):]
if remainder == "" || strings.ContainsRune(" :,", rune(remainder[0])) {
return strings.TrimLeft(remainder, " :,"), true
}
}
return "", false
}
func isMissingModelPhrase(value string) bool {
switch strings.Trim(value, " .!;\t\r\n") {
case "not found", "was not found", "could not be found", "does not exist", "doesn't exist", "not exist", "is unknown":
return true
default:
return false
}
}
// isRequestInvalidError returns true if the error represents a client request
// error that should not be retried. Specifically, it treats 400 responses with
// "invalid_request_error", request-scoped 404 item misses caused by `store=false`,

View File

@@ -2,6 +2,8 @@ package auth
import (
"context"
"errors"
"fmt"
"net/http"
"sync"
"testing"
@@ -162,6 +164,7 @@ type authFallbackExecutor struct {
streamCalls []string
executeErrors map[string]error
streamFirstErrors map[string]error
countTokenErrors map[string]error
}
func (e *authFallbackExecutor) Identifier() string {
@@ -200,8 +203,14 @@ func (e *authFallbackExecutor) Refresh(_ context.Context, auth *Auth) (*Auth, er
return auth, nil
}
func (e *authFallbackExecutor) CountTokens(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
return cliproxyexecutor.Response{}, &Error{HTTPStatus: 500, Message: "not implemented"}
func (e *authFallbackExecutor) CountTokens(_ context.Context, auth *Auth, _ cliproxyexecutor.Request, _ cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
e.mu.Lock()
err := e.countTokenErrors[auth.ID]
e.mu.Unlock()
if err != nil {
return cliproxyexecutor.Response{}, err
}
return cliproxyexecutor.Response{Payload: []byte(auth.ID)}, nil
}
func (e *authFallbackExecutor) HttpRequest(context.Context, *Auth, *http.Request) (*http.Response, error) {
@@ -224,6 +233,27 @@ func (e *authFallbackExecutor) StreamCalls() []string {
return out
}
type resultCaptureHook struct {
NoopHook
mu sync.Mutex
results []Result
}
func (h *resultCaptureHook) OnResult(_ context.Context, result Result) {
h.mu.Lock()
h.results = append(h.results, result)
h.mu.Unlock()
}
func (h *resultCaptureHook) Results() []Result {
h.mu.Lock()
defer h.mu.Unlock()
out := make([]Result, len(h.results))
copy(out, h.results)
return out
}
type retryAfterStatusError struct {
status int
message string
@@ -1269,6 +1299,369 @@ func TestManager_MarkResult_RequestScopedNotFoundDoesNotCooldownAuth(t *testing.
}
}
func TestManager_ExecuteCount_GenericRouteNotFoundDoesNotSuspendModel(t *testing.T) {
previous := quotaCooldownDisabled.Load()
quotaCooldownDisabled.Store(false)
t.Cleanup(func() { quotaCooldownDisabled.Store(previous) })
hook := &resultCaptureHook{}
m := NewManager(nil, nil, hook)
executor := &authFallbackExecutor{
id: "claude",
countTokenErrors: map[string]error{
"count-route-not-found-auth": &Error{
HTTPStatus: http.StatusNotFound,
Message: "404 page not found",
},
},
}
m.RegisterExecutor(executor)
model := "count-route-not-found-model"
auth := &Auth{ID: "count-route-not-found-auth", Provider: "claude"}
reg := registry.GetGlobalRegistry()
reg.RegisterClient(auth.ID, auth.Provider, []*registry.ModelInfo{{ID: model}})
t.Cleanup(func() { reg.UnregisterClient(auth.ID) })
if _, errRegister := m.Register(context.Background(), auth); errRegister != nil {
t.Fatalf("register auth: %v", errRegister)
}
if _, errCount := m.ExecuteCount(context.Background(), []string{"claude"}, cliproxyexecutor.Request{Model: model}, cliproxyexecutor.Options{}); errCount == nil {
t.Fatal("expected count_tokens route 404 error")
}
updated, ok := m.GetByID(auth.ID)
if !ok || updated == nil {
t.Fatal("expected auth to remain registered")
}
if updated.Failed != 1 {
t.Fatalf("failed request count = %d, want 1", updated.Failed)
}
results := hook.Results()
if len(results) != 1 || results[0].Success || results[0].Error == nil || results[0].Error.HTTPStatus != http.StatusNotFound {
t.Fatalf("recorded results = %#v, want one failed 404", results)
}
if updated.Unavailable {
t.Fatal("expected route 404 to keep auth available")
}
if state := updated.ModelStates[model]; state != nil {
t.Fatalf("expected route 404 to avoid model cooldown state, got %#v", state)
}
if count := reg.GetModelCount(model); count != 1 {
t.Fatalf("available model count = %d, want 1", count)
}
resp, errExecute := m.Execute(context.Background(), []string{"claude"}, cliproxyexecutor.Request{Model: model}, cliproxyexecutor.Options{})
if errExecute != nil {
t.Fatalf("execute after count_tokens route 404: %v", errExecute)
}
if string(resp.Payload) != auth.ID {
t.Fatalf("execute payload = %q, want %q", string(resp.Payload), auth.ID)
}
}
func TestManager_ExecuteCount_ExplicitModelNotFoundSuspendsModel(t *testing.T) {
previous := quotaCooldownDisabled.Load()
quotaCooldownDisabled.Store(false)
t.Cleanup(func() { quotaCooldownDisabled.Store(previous) })
hook := &resultCaptureHook{}
m := NewManager(nil, nil, hook)
executor := &authFallbackExecutor{
id: "claude",
countTokenErrors: map[string]error{
"count-model-not-found-auth": &Error{
Code: "model_not_found",
HTTPStatus: http.StatusNotFound,
Message: `{"type":"error","error":{"type":"not_found_error","message":"model count-explicitly-missing-model was not found"}}`,
},
},
}
m.RegisterExecutor(executor)
model := "count-explicitly-missing-model"
auth := &Auth{ID: "count-model-not-found-auth", Provider: "claude"}
reg := registry.GetGlobalRegistry()
reg.RegisterClient(auth.ID, auth.Provider, []*registry.ModelInfo{{ID: model}})
t.Cleanup(func() { reg.UnregisterClient(auth.ID) })
if _, errRegister := m.Register(context.Background(), auth); errRegister != nil {
t.Fatalf("register auth: %v", errRegister)
}
if _, errCount := m.ExecuteCount(context.Background(), []string{"claude"}, cliproxyexecutor.Request{Model: model}, cliproxyexecutor.Options{}); errCount == nil {
t.Fatal("expected count_tokens model-not-found error")
}
updated, ok := m.GetByID(auth.ID)
if !ok || updated == nil {
t.Fatal("expected auth to remain registered")
}
state := updated.ModelStates[model]
if state == nil || !state.Unavailable {
t.Fatalf("expected model-not-found cooldown state, got %#v", state)
}
if state.LastError == nil || state.LastError.Code != "model_not_found" {
t.Fatalf("model state error = %#v, want preserved model_not_found code", state.LastError)
}
results := hook.Results()
if len(results) != 1 || results[0].Error == nil || results[0].Error.Code != "model_not_found" {
t.Fatalf("hook results = %#v, want preserved model_not_found code", results)
}
remaining := time.Until(state.NextRetryAfter)
if remaining < 11*time.Hour || remaining > 12*time.Hour {
t.Fatalf("model-not-found cooldown = %v, want about 12h", remaining)
}
if count := reg.GetModelCount(model); count != 0 {
t.Fatalf("available model count = %d, want 0", count)
}
}
func TestIsCountTokensEndpointNotFoundError(t *testing.T) {
tests := []struct {
name string
err error
model string
want bool
}{
{
name: "empty router 404",
err: &Error{HTTPStatus: http.StatusNotFound},
want: true,
},
{
name: "plain router 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: "404 page not found"},
want: true,
},
{
name: "wrapped router 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: "upstream request failed: 404 page not found"},
want: true,
},
{
name: "fastapi route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"detail":"Not Found"}`},
want: true,
},
{
name: "problem details route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"title":"Not Found","status":404}`},
want: true,
},
{
name: "nested generic route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"Not Found"}}`},
want: true,
},
{
name: "generic model api route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"type":"not_found_error","title":"Model API","detail":"Not Found"}`},
want: true,
},
{
name: "generic model metadata route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"model metadata route not found"}}`},
want: true,
},
{
name: "generic model provider 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"model provider was not found"}}`},
want: true,
},
{
name: "generic route with misleading metadata",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"message":"Not Found","request_id":"model_not_found"}`},
want: true,
},
{
name: "express count route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: "Cannot POST /v1/messages/count_tokens"},
want: true,
},
{
name: "html route 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: "<html><title>404 Not Found</title></html>"},
want: true,
},
{
name: "structured model 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"model claude-missing was not found"}}`},
want: false,
},
{
name: "anthropic exact model reference",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"model: claude-missing"}}`},
want: false,
},
{
name: "anthropic model reference with thinking suffix",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"model: claude-missing"}}`},
model: "claude-missing(high)",
want: false,
},
{
name: "requested model does not exist",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"The requested model does not exist"}}`},
want: false,
},
{
name: "requested quoted model could not be found",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":{"type":"not_found_error","message":"The requested model 'foo' could not be found"}}`},
model: "foo",
want: false,
},
{
name: "problem details model type uri",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"type":"https://example.com/problems/model-not-found","title":"Not Found","status":404}`},
want: false,
},
{
name: "structured model error string",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"error":"model claude-missing does not exist"}`},
want: false,
},
{
name: "model code with generic message",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"message":"Not Found","code":"model_not_found","model":"claude-missing"}`},
want: false,
},
{
name: "typed model not found code",
err: &Error{Code: "model_not_found", HTTPStatus: http.StatusNotFound, Message: "Not Found"},
want: false,
},
{
name: "typed wrapper with structured model code",
err: &Error{Code: "not_found", HTTPStatus: http.StatusNotFound, Message: `{"error":{"code":"model_not_found","message":"Not Found"}}`},
want: false,
},
{
name: "wrapped structured model code",
err: fmt.Errorf("upstream failed: %w", &requestScopedStatusError{
status: http.StatusNotFound,
message: `{"error":{"code":"model_not_found","message":"Not Found"}}`,
}),
want: false,
},
{
name: "joined structured model code",
err: errors.Join(
errors.New("upstream failed"),
&requestScopedStatusError{
status: http.StatusNotFound,
message: `{"error":{"code":"model_not_found","message":"Not Found"}}`,
},
),
want: false,
},
{
name: "outer generic inner model 404",
err: &Error{HTTPStatus: http.StatusNotFound, Message: `{"message":"Not Found","error":{"type":"not_found_error","message":"model claude-missing does not exist"}}`},
want: false,
},
{
name: "unstructured model text",
err: &Error{HTTPStatus: http.StatusNotFound, Message: "model claude-missing was not found"},
want: true,
},
{
name: "non 404",
err: &Error{HTTPStatus: http.StatusInternalServerError, Message: "404 page not found"},
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
model := tc.model
if model == "" {
model = "claude-missing"
}
if got := isCountTokensEndpointNotFoundError(tc.err, model); got != tc.want {
t.Fatalf("isCountTokensEndpointNotFoundError() = %v, want %v", got, tc.want)
}
})
}
}
func TestManager_Execute_GenericRouteNotFoundStillSuspendsModel(t *testing.T) {
previous := quotaCooldownDisabled.Load()
quotaCooldownDisabled.Store(false)
t.Cleanup(func() { quotaCooldownDisabled.Store(previous) })
m := NewManager(nil, nil, nil)
executor := &authFallbackExecutor{
id: "claude",
executeErrors: map[string]error{
"messages-route-not-found-auth": &Error{
HTTPStatus: http.StatusNotFound,
Message: "404 page not found",
},
},
}
m.RegisterExecutor(executor)
model := "messages-route-not-found-model"
auth := &Auth{ID: "messages-route-not-found-auth", Provider: "claude"}
reg := registry.GetGlobalRegistry()
reg.RegisterClient(auth.ID, auth.Provider, []*registry.ModelInfo{{ID: model}})
t.Cleanup(func() { reg.UnregisterClient(auth.ID) })
if _, errRegister := m.Register(context.Background(), auth); errRegister != nil {
t.Fatalf("register auth: %v", errRegister)
}
if _, errExecute := m.Execute(context.Background(), []string{"claude"}, cliproxyexecutor.Request{Model: model}, cliproxyexecutor.Options{}); errExecute == nil {
t.Fatal("expected messages route 404")
}
updated, ok := m.GetByID(auth.ID)
if !ok || updated == nil {
t.Fatal("expected auth to remain registered")
}
state := updated.ModelStates[model]
if state == nil || !state.Unavailable || state.NextRetryAfter.IsZero() {
t.Fatalf("expected ordinary messages 404 to suspend model, got %#v", state)
}
}
func TestManager_RecordResult_AvailabilityNeutralSkipsSchedulerUpdate(t *testing.T) {
m := NewManager(nil, nil, nil)
auth := &Auth{ID: "availability-neutral-auth", Provider: "claude"}
if _, errRegister := m.Register(context.Background(), auth); errRegister != nil {
t.Fatalf("register auth: %v", errRegister)
}
m.scheduler.mu.Lock()
provider := m.scheduler.providers[auth.Provider]
if provider == nil || provider.auths[auth.ID] == nil {
m.scheduler.mu.Unlock()
t.Fatal("expected scheduler auth metadata")
}
before := provider.auths[auth.ID].auth
m.scheduler.mu.Unlock()
m.recordAvailabilityNeutralResult(context.Background(), Result{
AuthID: auth.ID,
Provider: auth.Provider,
Model: "availability-neutral-model",
Success: false,
Error: &Error{HTTPStatus: http.StatusNotFound, Message: "404 page not found"},
})
updated, ok := m.GetByID(auth.ID)
if !ok || updated == nil || updated.Failed != 1 {
t.Fatalf("updated auth = %#v, want one recorded failure", updated)
}
m.scheduler.mu.Lock()
after := m.scheduler.providers[auth.Provider].auths[auth.ID].auth
m.scheduler.mu.Unlock()
if after != before {
t.Fatal("availability-neutral result unexpectedly replaced scheduler auth snapshot")
}
}
func TestManager_RequestScopedNotFoundStopsRetryWithoutSuspendingAuth(t *testing.T) {
m := NewManager(nil, nil, nil)
executor := &authFallbackExecutor{