diff --git a/src/codex/catalog/metadata.ts b/src/codex/catalog/metadata.ts index c5550bf373..80ba299fc0 100644 --- a/src/codex/catalog/metadata.ts +++ b/src/codex/catalog/metadata.ts @@ -253,11 +253,11 @@ export function shouldUpgradeToUpstreamEntry(entry: RawEntry): boolean { } export function nativeOpenAiSlugs(): string[] { - const live = listCatalogNativeSlugs(); + const live = catalogNativeSlugs(); return live.length > 0 ? unique([...live, ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]) : NATIVE_OPENAI_MODELS; } -export function listCatalogNativeSlugs(): string[] { +function catalogNativeSlugs(): string[] { const cat = readCurrentCatalogOrCache(); const models = cat?.models ?? []; const live = models.flatMap(entry => { @@ -271,7 +271,11 @@ export function listCatalogNativeSlugs(): string[] { // Deliberately ignore `visibility`: it is a rendered projection of disabledModels and account // selectors, so treating it as fresh availability would shrink the supported set between syncs. // visibleNativeSlugs applies the current disabledModels source of truth for public consumers. + return unique([...live, ...accountBound]); +} + +export function listCatalogNativeSlugs(): string[] { // Ensure documented additions (e.g. gpt-5.3-codex-spark) appear even when the bundled catalog // predates the slug — mirrors nativeOpenAiSlugs() which already merges them for /v1/models. - return unique([...live, ...accountBound, ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]); + return unique([...catalogNativeSlugs(), ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]); } diff --git a/tests/codex-catalog-sync-hardening.test.ts b/tests/codex-catalog-sync-hardening.test.ts index 4a1bf62a90..b0487a26ca 100644 --- a/tests/codex-catalog-sync-hardening.test.ts +++ b/tests/codex-catalog-sync-hardening.test.ts @@ -479,6 +479,23 @@ describe("Codex catalog sync hardening", () => { expect(rows.some(row => row.slug.startsWith("team/"))).toBe(false); }); + test("native model fallback remains reachable without a live catalog", () => { + writeFileSync( + join(codexHome, "config.toml"), + 'model_catalog_json = "missing-catalog.json"\n', + "utf8", + ); + const r = runScript(codexHome, opencodexHome, ` + const { listCatalogNativeSlugs, nativeOpenAiSlugs, NATIVE_OPENAI_MODELS } = await import("./src/codex/catalog"); + console.log(JSON.stringify({ picker: listCatalogNativeSlugs(), native: nativeOpenAiSlugs(), fallback: NATIVE_OPENAI_MODELS })); + `); + + expect(r.status).toBe(0); + const result = JSON.parse(r.stdout) as { picker: string[]; native: string[]; fallback: string[] }; + expect(result.picker).toContain("gpt-5.3-codex-spark"); + expect(result.native).toEqual(result.fallback); + }); + test("account sync recovers supported natives that were hidden before selectors existed", () => { const catalogPath = join(codexHome, "catalog.json"); writeFileSync(join(codexHome, "config.toml"), 'model_catalog_json = "catalog.json"\n', "utf8");