From d963a756c34b4692c443845b754badae101f91b7 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 10 Jun 2026 21:48:05 -0700 Subject: [PATCH] FN-6205: skip custom providers in settings save split Prevent the settings save split from serializing custom provider definitions through the global or project patch paths. - skip `customProviders` when building global settings patches - skip `customProviders` when building project settings patches - add a regression test that keeps custom providers out of both serialized patches Files changed: .../app/__tests__/settings-save-split.test.ts | 24 ++++++++++++++++++++++ .../app/components/settings/save-split.ts | 4 ++++ 2 files changed, 28 insertions(+) Fusion-Task-Id: FN-6205 Fusion-Task-Lineage: 84c3f42b-c621-44ff-b977-ecd3a6729e04 --- .../app/__tests__/settings-save-split.test.ts | 24 +++++++++++++++++++ .../app/components/settings/save-split.ts | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/packages/dashboard/app/__tests__/settings-save-split.test.ts b/packages/dashboard/app/__tests__/settings-save-split.test.ts index ea5f3fc652..97a4375470 100644 --- a/packages/dashboard/app/__tests__/settings-save-split.test.ts +++ b/packages/dashboard/app/__tests__/settings-save-split.test.ts @@ -117,6 +117,30 @@ describe("splitSettingsSave", () => { expect(projectPatch).toEqual({ enabledBuiltinWorkflowIds: ["builtin:coding"] }); }); + it("excludes customProviders from both global and project patches", () => { + expect(isGlobalSettingsKey("customProviders")).toBe(true); + + const { globalPatch, projectPatch } = splitSettingsSave({ + payload: { + customProviders: [ + { + id: "x", + name: "Provider X", + baseUrl: "https://example.test/v1", + apiKey: "secret", + models: [{ id: "model-x", name: "Model X" }], + }, + ], + }, + initialValues: { customProviders: [] } as never, + initialScopedValues: { global: { customProviders: [] }, project: {} } as never, + activeSection: "authentication", + }); + + expect("customProviders" in globalPatch).toBe(false); + expect("customProviders" in projectPatch).toBe(false); + }); + it("emits null-as-delete when a project override is cleared", () => { const initialScopedValues = { global: {}, diff --git a/packages/dashboard/app/components/settings/save-split.ts b/packages/dashboard/app/components/settings/save-split.ts index f2db7c676f..60afda1c28 100644 --- a/packages/dashboard/app/components/settings/save-split.ts +++ b/packages/dashboard/app/components/settings/save-split.ts @@ -76,6 +76,9 @@ export function splitSettingsSave({ if (key === "persistAgentThinkingLog") { continue; } + if (key === "customProviders") { + continue; + } if (isGlobalSettingsKey(key)) { // null-as-delete: explicit clear is sent as null, plain undefined dropped. const initialValue = initialValues?.[key as keyof GlobalSettings]; @@ -90,6 +93,7 @@ export function splitSettingsSave({ const projectPatch: Partial = {}; for (const [key, value] of Object.entries(payload)) { if (key === "githubTokenConfigured" || key === "prAuthAvailable") continue; // server-only + if (key === "customProviders") continue; if (key === "githubTrackingDefaultRepo" && activeSection === "global-general") continue; if (!isProjectSettingsKey(key)) continue;