From fabf15b5b92ae1db62e5c1a36e6afb12cbfb0bd2 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 6 Sep 2026 02:45:19 -0700 Subject: [PATCH] fix(server): skip disabled provider instances for text generation fallback fallbackTextGenerationProvider read only the legacy providers map, which decodes to defaults when the Providers UI has only written providerInstances. A codex instance disabled in Settings was still picked for thread titles, branch names, and commit messages. Check the providerInstances entry for each driver first and only fall back to the legacy providers map when there is no instance entry. Closes #7533 Co-Authored-By: Claude Fable 5.1 --- apps/server/src/serverSettings.test.ts | 18 ++++++++++++++++++ apps/server/src/serverSettings.ts | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/server/src/serverSettings.test.ts b/apps/server/src/serverSettings.test.ts index 6e837308e8cb..9769ebf0a5f6 100644 --- a/apps/server/src/serverSettings.test.ts +++ b/apps/server/src/serverSettings.test.ts @@ -656,6 +656,24 @@ it.layer(NodeServices.layer)("server settings", (it) => { }).pipe(Effect.provide(makeServerSettingsLayer())), ); + it.effect("skips a disabled provider instance when picking the text generation fallback", () => + Effect.gen(function* () { + const serverConfig = yield* ServerConfig.ServerConfig; + const fileSystem = yield* FileSystem.FileSystem; + const serverSettings = yield* ServerSettingsModule.ServerSettingsService; + // The Providers UI writes providerInstances only, so the legacy providers + // map decodes to defaults where codex is enabled and listed first. + yield* fileSystem.writeFileString( + serverConfig.settingsPath, + '{"providerInstances":{"codex":{"driver":"codex","enabled":false,"config":{}}}}', + ); + + const settings = yield* serverSettings.getSettings; + + assert.equal(settings.textGenerationModelSelection.instanceId, "claudeAgent"); + }).pipe(Effect.provide(makeServerSettingsLayer())), + ); + it.effect("keeps unused providers disabled in existing sparse settings files", () => Effect.gen(function* () { const serverConfig = yield* ServerConfig.ServerConfig; diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index 5f2550534883..84e978320a30 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -21,6 +21,7 @@ import { type UsageLimitSourceConfig, ProviderDriverKind, ProviderInstanceId, + resolveProviderInstanceEnabled, ServerSettings, ServerSettingsError, type ServerSettingsPatch, @@ -321,7 +322,13 @@ function resolveTextGenerationProvider(settings: ServerSettings): ServerSettings } function fallbackTextGenerationProvider(settings: ServerSettings): ServerSettings { - const fallbackEntry = Object.entries(settings.providers).find(([, provider]) => provider.enabled); + // Same precedence as isModelSelectionProviderEnabled: an explicit provider + // instance wins over the legacy providers map, which decodes to defaults + // (codex enabled) when the Providers UI has only written providerInstances. + const fallbackEntry = Object.entries(settings.providers).find(([driver, provider]) => { + const instance = settings.providerInstances[ProviderInstanceId.make(driver)]; + return instance === undefined ? provider.enabled : resolveProviderInstanceEnabled(instance); + }); const fallback = fallbackEntry ? ProviderDriverKind.make(fallbackEntry[0]) : undefined; if (!fallback) { return settings;