diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index b88220c15..fecfc4280 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -482,7 +482,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco base: prov.baseUrl ?? "", adapter: prov.adapter ?? "", models: [...(prov.models ?? [])].sort(), - selected: [...(prov.selectedModels ?? [])].sort(), + selected: Array.isArray(prov.selectedModels) ? [...prov.selectedModels].sort() : [], defaultModel: prov.defaultModel ?? null, ctx: prov.contextWindow ?? null, ctxW: prov.modelContextWindows ?? null, diff --git a/tests/gather-routed-models-single-flight.test.ts b/tests/gather-routed-models-single-flight.test.ts index 273d3b97b..a681a0784 100644 --- a/tests/gather-routed-models-single-flight.test.ts +++ b/tests/gather-routed-models-single-flight.test.ts @@ -329,6 +329,27 @@ describe("gatherRoutedModels single-flight", () => { expect(filterCatalogVisibleModels(withSelModels, withSel).map(m => m.id)).toEqual(["keep-me"]); }); + test("malformed persisted selectedModels does not break catalog gathering", async () => { + const config = { + port: 10100, + defaultProvider: "p", + providers: { + p: { + adapter: "openai-chat", + baseUrl: "https://sel.example.test/v1", + models: ["configured-model"], + liveModels: false, + selectedModels: 42, + }, + }, + } as unknown as OcxConfig; + + const models = await gatherRoutedModels(config); + + expect(models.map(model => model.id)).toEqual(["configured-model"]); + expect(filterCatalogVisibleModels(models, config).map(model => model.id)).toEqual(["configured-model"]); + }); + test("disabledModels-only config changes do not share a flight", async () => { let fetchCount = 0; let release!: () => void;