authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-06 02:56:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 02:56:28-07:00
log62f568b88b58e1e7cf422c21ee2e08f10977b714
treefa5776a23aa6a8a1dd0183cfc897893a0c79e6fc
parenteb8ed80300bd719b49fc80ad8708f9839bb48c92
signature Signed by PGP key B5690EEEBB952194

fix(server): skip disabled provider instances for text generation fallback (#10346)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

2 files changed, 26 insertions(+), 1 deletions(-)

apps/server/src/serverSettings.test.ts+18
...@@ -656,6 +656,24 @@ it.layer(NodeServices.layer)("server settings", (it) => {...@@ -656,6 +656,24 @@ it.layer(NodeServices.layer)("server settings", (it) => {
656 }).pipe(Effect.provide(makeServerSettingsLayer())),656 }).pipe(Effect.provide(makeServerSettingsLayer())),
657 );657 );
658658
659 it.effect("skips a disabled provider instance when picking the text generation fallback", () =>
660 Effect.gen(function* () {
661 const serverConfig = yield* ServerConfig.ServerConfig;
662 const fileSystem = yield* FileSystem.FileSystem;
663 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
664 // The Providers UI writes providerInstances only, so the legacy providers
665 // map decodes to defaults where codex is enabled and listed first.
666 yield* fileSystem.writeFileString(
667 serverConfig.settingsPath,
668 '{"providerInstances":{"codex":{"driver":"codex","enabled":false,"config":{}}}}',
669 );
670
671 const settings = yield* serverSettings.getSettings;
672
673 assert.equal(settings.textGenerationModelSelection.instanceId, "claudeAgent");
674 }).pipe(Effect.provide(makeServerSettingsLayer())),
675 );
676
659 it.effect("keeps unused providers disabled in existing sparse settings files", () =>677 it.effect("keeps unused providers disabled in existing sparse settings files", () =>
660 Effect.gen(function* () {678 Effect.gen(function* () {
661 const serverConfig = yield* ServerConfig.ServerConfig;679 const serverConfig = yield* ServerConfig.ServerConfig;
apps/server/src/serverSettings.ts+8-1
...@@ -21,6 +21,7 @@ import {...@@ -21,6 +21,7 @@ import {
21 type UsageLimitSourceConfig,21 type UsageLimitSourceConfig,
22 ProviderDriverKind,22 ProviderDriverKind,
23 ProviderInstanceId,23 ProviderInstanceId,
24 resolveProviderInstanceEnabled,
24 ServerSettings,25 ServerSettings,
25 ServerSettingsError,26 ServerSettingsError,
26 type ServerSettingsPatch,27 type ServerSettingsPatch,
...@@ -321,7 +322,13 @@ function resolveTextGenerationProvider(settings: ServerSettings): ServerSettings...@@ -321,7 +322,13 @@ function resolveTextGenerationProvider(settings: ServerSettings): ServerSettings
321}322}
322323
323function fallbackTextGenerationProvider(settings: ServerSettings): ServerSettings {324function fallbackTextGenerationProvider(settings: ServerSettings): ServerSettings {
324 const fallbackEntry = Object.entries(settings.providers).find(([, provider]) => provider.enabled);325 // Same precedence as isModelSelectionProviderEnabled: an explicit provider
326 // instance wins over the legacy providers map, which decodes to defaults
327 // (codex enabled) when the Providers UI has only written providerInstances.
328 const fallbackEntry = Object.entries(settings.providers).find(([driver, provider]) => {
329 const instance = settings.providerInstances[ProviderInstanceId.make(driver)];
330 return instance === undefined ? provider.enabled : resolveProviderInstanceEnabled(instance);
331 });
325 const fallback = fallbackEntry ? ProviderDriverKind.make(fallbackEntry[0]) : undefined;332 const fallback = fallbackEntry ? ProviderDriverKind.make(fallbackEntry[0]) : undefined;
326 if (!fallback) {333 if (!fallback) {
327 return settings;334 return settings;