From 54f244bce78344756d6ba313bb331a419666b477 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Wed, 20 May 2026 10:05:49 -0700 Subject: [PATCH] fix(core): preserve oauth auth when saving provider settings --- .../storage/provider-settings-manager.test.ts | 81 +++++++++++++++++++ .../storage/provider-settings-manager.ts | 28 ++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/sdk/packages/core/src/services/storage/provider-settings-manager.test.ts b/sdk/packages/core/src/services/storage/provider-settings-manager.test.ts index d930f4ab0c..5d145692ee 100644 --- a/sdk/packages/core/src/services/storage/provider-settings-manager.test.ts +++ b/sdk/packages/core/src/services/storage/provider-settings-manager.test.ts @@ -337,6 +337,87 @@ describe("ProviderSettingsManager", () => { expect(manager.read().providers["openai-codex"]?.tokenSource).toBe("oauth"); }); + it("preserves OAuth auth when updating only Cline model settings", () => { + const tempDir = mkdtempSync( + path.join(os.tmpdir(), "core-provider-settings-"), + ); + tempDirs.push(tempDir); + const filePath = path.join(tempDir, "provider-settings.json"); + const manager = new ProviderSettingsManager({ filePath }); + + manager.saveProviderSettings( + { + provider: "cline", + model: "anthropic/claude-sonnet-4.6", + auth: { + accessToken: "workos:access-old", + refreshToken: "refresh-old", + expiresAt: 4_000_000_000_000, + accountId: "acct-old", + }, + }, + { tokenSource: "oauth" }, + ); + + manager.saveProviderSettings({ + provider: "cline", + model: "anthropic/claude-haiku-4.5", + reasoning: { enabled: false }, + }); + + expect(manager.getProviderSettings("cline")).toEqual({ + provider: "cline", + model: "anthropic/claude-haiku-4.5", + reasoning: { enabled: false }, + auth: { + accessToken: "workos:access-old", + refreshToken: "refresh-old", + expiresAt: 4_000_000_000_000, + accountId: "acct-old", + }, + }); + expect(manager.read().providers.cline?.tokenSource).toBe("oauth"); + }); + + it("merges partial OAuth auth updates with existing refresh metadata", () => { + const tempDir = mkdtempSync( + path.join(os.tmpdir(), "core-provider-settings-"), + ); + tempDirs.push(tempDir); + const filePath = path.join(tempDir, "provider-settings.json"); + const manager = new ProviderSettingsManager({ filePath }); + + manager.saveProviderSettings( + { + provider: "openai-codex", + auth: { + accessToken: "access-old", + refreshToken: "refresh-old", + expiresAt: 4_000_000_000_000, + accountId: "acct-old", + }, + }, + { tokenSource: "oauth" }, + ); + + manager.saveProviderSettings( + { + provider: "openai-codex", + auth: { + accessToken: "access-new", + }, + }, + { tokenSource: "oauth" }, + ); + + expect(manager.getProviderSettings("openai-codex")?.auth).toEqual({ + accessToken: "access-new", + refreshToken: "refresh-old", + expiresAt: 4_000_000_000_000, + accountId: "acct-old", + }); + }); + it("ignores invalid persisted JSON and falls back to empty state", () => { const tempDir = mkdtempSync( path.join(os.tmpdir(), "core-provider-settings-"), diff --git a/sdk/packages/core/src/services/storage/provider-settings-manager.ts b/sdk/packages/core/src/services/storage/provider-settings-manager.ts index e85bb7d06d..83d8f7106f 100644 --- a/sdk/packages/core/src/services/storage/provider-settings-manager.ts +++ b/sdk/packages/core/src/services/storage/provider-settings-manager.ts @@ -6,6 +6,7 @@ import { writeFileSync, } from "node:fs"; import { basename, dirname } from "node:path"; +import { isOAuthProviderId } from "@cline/shared"; import { resolveProviderSettingsPath } from "@cline/shared/storage"; import { getLiveModelsCatalog } from "../.."; import { @@ -50,6 +51,25 @@ function inferLegacyDataDir(filePath: string): string | undefined { return dirname(settingsDir); } +function preserveOAuthAuthFields( + next: ProviderSettings, + previous: ProviderSettings | undefined, +): ProviderSettings { + if (!isOAuthProviderId(next.provider) || !previous?.auth) { + return next; + } + if (!next.auth) { + return { ...next, auth: previous.auth }; + } + return { + ...next, + auth: { + ...previous.auth, + ...next.auth, + }, + }; +} + export class ProviderSettingsManager { private readonly filePath: string; private readonly dataDir?: string; @@ -124,11 +144,15 @@ export class ProviderSettingsManager { settings: unknown, options: SaveProviderSettingsOptions = {}, ): StoredProviderSettings { - const validatedSettings = ProviderSettingsSchema.parse(settings); + const parsedSettings = ProviderSettingsSchema.parse(settings); const previous = this.read(); - const providerId = validatedSettings.provider; + const providerId = parsedSettings.provider; const shouldSetLastUsed = options.setLastUsed !== false; const previousEntry = previous.providers[providerId]; + const validatedSettings = preserveOAuthAuthFields( + parsedSettings, + previousEntry?.settings, + ); const tokenSource = options.tokenSource ?? previousEntry?.tokenSource ?? "manual"; const next: StoredProviderSettings = {