fix(auth): keep oauth fallback recovery consistent

This commit is contained in:
Vincent Koc
2026-04-17 15:16:42 -07:00
committed by Peter Steinberger
parent d97d5c04f0
commit f98e98ab66
2 changed files with 53 additions and 7 deletions

View File

@@ -334,6 +334,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
if (
mainCred?.type === "oauth" &&
mainCred.provider === params.credential.provider &&
hasUsableOAuthCredential(mainCred) &&
Number.isFinite(mainCred.expires) &&
(!Number.isFinite(params.credential.expires) ||
mainCred.expires > params.credential.expires) &&
@@ -651,13 +652,18 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
credential: recovered,
};
}
const retried = await refreshOAuthTokenWithLock({
profileId: params.profileId,
provider: params.credential.provider,
agentDir: params.agentDir,
});
if (retried) {
return retried;
try {
const retried = await refreshOAuthTokenWithLock({
profileId: params.profileId,
provider: params.credential.provider,
agentDir: params.agentDir,
});
if (retried) {
return retried;
}
} catch {
// Retry failed too; keep flowing through the main-store fallback
// and final wrapped error path below.
}
}
if (params.agentDir) {

View File

@@ -332,4 +332,44 @@ describe("resolveApiKeyForProfile fallback to main agent", () => {
/OAuth token refresh failed/,
);
});
it("still falls back to main agent credentials when the refresh-token-reused retry throws", async () => {
const profileId = "anthropic:claude-cli";
const now = Date.now();
const expiredTime = now - 60 * 60 * 1000;
const freshTime = now + 60 * 60 * 1000;
await writeAuthProfilesStore(
secondaryAgentDir,
createOauthStore({
profileId,
access: "expired-access-token",
refresh: "expired-refresh-token",
expires: expiredTime,
}),
);
await writeAuthProfilesStore(
mainAgentDir,
createOauthStore({
profileId,
access: "fresh-access-token",
refresh: "fresh-refresh-token",
expires: freshTime,
}),
);
getOAuthApiKeyMock
.mockImplementationOnce(async () => {
throw new Error("refresh_token_reused");
})
.mockImplementationOnce(async () => {
throw new Error("retry also failed");
});
const result = await resolveFromSecondaryAgent(profileId);
expect(result?.apiKey).toBe("fresh-access-token");
expect(result?.provider).toBe("anthropic");
});
});