From 90afb9cb73e2e881780f213c99d75459a4a6eef3 Mon Sep 17 00:00:00 2001 From: DragonFSKY Date: Mon, 9 Mar 2026 03:11:47 +0800 Subject: [PATCH] fix(auth): new OAuth accounts invisible to scheduler after dynamic registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When new OAuth auth files are added while the service is running, `applyCoreAuthAddOrUpdate` calls `coreManager.Register()` (which upserts into the scheduler) BEFORE `registerModelsForAuth()`. At upsert time, `buildScheduledAuthMeta` snapshots `supportedModelSetForAuth` from the global model registry — but models haven't been registered yet, so the set is empty. With an empty `supportedModelSet`, `supportsModel()` always returns false and the new auth is never added to any model shard. Additionally, when all existing accounts are in cooldown, the scheduler returns `modelCooldownError`, but `shouldRetrySchedulerPick` only handles `*Error` types — so the `syncScheduler` safety-net rebuild never triggers and the new accounts remain invisible. Fix: 1. Add `RefreshSchedulerEntry()` to re-upsert a single auth after its models are registered, rebuilding `supportedModelSet` from the now-populated registry. 2. Call it from `applyCoreAuthAddOrUpdate` after `registerModelsForAuth`. 3. Make `shouldRetrySchedulerPick` also match `*modelCooldownError` so the full scheduler rebuild triggers when all credentials are cooling down — catching any similar stale-snapshot edge cases. --- sdk/cliproxy/auth/conductor.go | 24 ++++++++++++++++++++++++ sdk/cliproxy/service.go | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index aacf9322..b29e04db 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -213,6 +213,26 @@ func (m *Manager) syncScheduler() { m.syncSchedulerFromSnapshot(m.snapshotAuths()) } +// RefreshSchedulerEntry re-upserts a single auth into the scheduler so that its +// supportedModelSet is rebuilt from the current global model registry state. +// This must be called after models have been registered for a newly added auth, +// because the initial scheduler.upsertAuth during Register/Update runs before +// registerModelsForAuth and therefore snapshots an empty model set. +func (m *Manager) RefreshSchedulerEntry(authID string) { + if m == nil || m.scheduler == nil || authID == "" { + return + } + m.mu.RLock() + auth, ok := m.auths[authID] + if !ok || auth == nil { + m.mu.RUnlock() + return + } + snapshot := auth.Clone() + m.mu.RUnlock() + m.scheduler.upsertAuth(snapshot) +} + func (m *Manager) SetSelector(selector Selector) { if m == nil { return @@ -2038,6 +2058,10 @@ func shouldRetrySchedulerPick(err error) bool { if err == nil { return false } + var cooldownErr *modelCooldownError + if errors.As(err, &cooldownErr) { + return true + } var authErr *Error if !errors.As(err, &authErr) || authErr == nil { return false diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 6124f8b1..10cc35f3 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -312,6 +312,12 @@ func (s *Service) applyCoreAuthAddOrUpdate(ctx context.Context, auth *coreauth.A // This operation may block on network calls, but the auth configuration // is already effective at this point. s.registerModelsForAuth(auth) + + // Refresh the scheduler entry so that the auth's supportedModelSet is rebuilt + // from the now-populated global model registry. Without this, newly added auths + // have an empty supportedModelSet (because Register/Update upserts into the + // scheduler before registerModelsForAuth runs) and are invisible to the scheduler. + s.coreManager.RefreshSchedulerEntry(auth.ID) } func (s *Service) applyCoreAuthRemoval(ctx context.Context, id string) {