fix(auth): refresh all OAuth profiles per provider

Previously, resolveOAuthToken() would return after finding the first
valid profile for a provider, leaving other profiles expired. This
caused multi-account setups to only refresh one account per provider.

Additionally, the status display would show stale expiry times because
it read credentials before the usage fetch triggered refreshes.

Changes:
- Renamed resolveOAuthToken() to resolveOAuthTokens() and changed it
  to collect all valid profiles instead of returning early
- Updated resolveProviderAuths() to spread all resolved tokens
- Added store re-read in list.status-command.ts after usage fetch
  to display refreshed credential state

Fixes #3803
This commit is contained in:
Dave Walker 2026-01-29 08:15:00 +00:00
parent 4583f88626
commit 7dad13cd3a
3 changed files with 23 additions and 9 deletions

View File

@ -14,6 +14,7 @@ Status: beta.
- Memory Search: allow extra paths for memory indexing. (#3600) Thanks @kira-ariaki.
### Changes
- Auth: fix OAuth refresh only refreshing one profile per provider, leaving other accounts expired. (#3803)
- Providers: add Venice AI integration; update Moonshot Kimi references to kimi-k2.5; update MiniMax API endpoint/format. (#2762, #3064)
- Providers: add Xiaomi MiMo (mimo-v2-flash) support and onboarding flow. (#3454) Thanks @WqyJh.
- Telegram: quote replies, edit-message action, silent sends, sticker support + vision caching, linkPreview toggle, plugin sendPayload support. (#2900, #2394, #2382, #2548, #1700, #1917)

View File

@ -528,6 +528,18 @@ export async function modelsStatusCommand(
}
}
// Re-read auth health after usage fetch, which may have refreshed expired tokens
const refreshedStore = ensureAuthProfileStore();
const refreshedHealth = buildAuthHealthSummary({
store: refreshedStore,
cfg,
warnAfterMs: DEFAULT_OAUTH_WARN_MS,
providers,
});
const refreshedOauthProfiles = refreshedHealth.profiles.filter(
(profile) => profile.type === "oauth" || profile.type === "token",
);
const formatStatus = (status: string) => {
if (status === "ok") return colorize(rich, theme.success, "ok");
if (status === "static") return colorize(rich, theme.muted, "static");
@ -536,8 +548,8 @@ export async function modelsStatusCommand(
return colorize(rich, theme.error, "expired");
};
const profilesByProvider = new Map<string, typeof oauthProfiles>();
for (const profile of oauthProfiles) {
const profilesByProvider = new Map<string, typeof refreshedOauthProfiles>();
for (const profile of refreshedOauthProfiles) {
const current = profilesByProvider.get(profile.provider);
if (current) current.push(profile);
else profilesByProvider.set(profile.provider, [profile]);

View File

@ -123,10 +123,10 @@ function resolveXiaomiApiKey(): string | undefined {
return undefined;
}
async function resolveOAuthToken(params: {
async function resolveOAuthTokens(params: {
provider: UsageProviderId;
agentDir?: string;
}): Promise<ProviderAuth | null> {
}): Promise<ProviderAuth[]> {
const cfg = loadConfig();
const store = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
@ -143,6 +143,7 @@ async function resolveOAuthToken(params: {
if (!deduped.includes(entry)) deduped.push(entry);
}
const results: ProviderAuth[] = [];
for (const profileId of deduped) {
const cred = store.profiles[profileId];
if (!cred || (cred.type !== "oauth" && cred.type !== "token")) continue;
@ -161,20 +162,20 @@ async function resolveOAuthToken(params: {
const parsed = parseGoogleToken(resolved.apiKey);
token = parsed?.token ?? resolved.apiKey;
}
return {
results.push({
provider: params.provider,
token,
accountId:
cred.type === "oauth" && "accountId" in cred
? (cred as { accountId?: string }).accountId
: undefined,
};
});
} catch {
// ignore
}
}
return null;
return results;
}
function resolveOAuthProviders(agentDir?: string): UsageProviderId[] {
@ -233,11 +234,11 @@ export async function resolveProviderAuths(params: {
}
if (!oauthProviders.includes(provider)) continue;
const auth = await resolveOAuthToken({
const resolved = await resolveOAuthTokens({
provider,
agentDir: params.agentDir,
});
if (auth) auths.push(auth);
auths.push(...resolved);
}
return auths;