From 87d9c5d60e3beee6603a77fca05f81721776f473 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Sat, 29 Aug 2026 17:35:28 -0700 Subject: [PATCH 1/4] fix: drop gpt-5.4 and gpt-5.4-mini from the subscription allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both retire from the ChatGPT-subscription model picker at 2026-08-31T19:00:00Z. `openai/codex`'s shipped catalog (codex-rs/models-manager/models.json, fetched first-hand) marks both `visibility: "hide"` with `upgrade.retirement_at: "2026-08-31T19:00:00Z"` and names the replacements: gpt-5.4 -> gpt-5.6-terra, gpt-5.4-mini -> gpt-5.6-luna. Both replacements are already in `OAUTH_ALLOWED_MODELS`, so affected users land on a working model with no further change. This is a subscription-picker retirement, NOT an API deprecation: both ids still carry `supported_in_api: true`, neither is on OpenAI's deprecations page, and models.dev marks neither `deprecated`. The filter only runs when `auth.type === "oauth"`, so API-key users are unaffected. It will not self-heal. models.dev hard-deletes an id only once it stops serving entirely, and these remain live API models, so the catalog keeps them. Left in the allowlist they would sit in the subscription picker past the deadline and fail at request time with the same opaque 400 that #1179 rebuilt this list to prevent. Tests: gpt-5.4 / gpt-5.4-mini move out of VERIFIED_ACCEPTED into a new RETIRED_FROM_SUBSCRIPTION constant rather than into VERIFIED_REJECTED — they probed HTTP 200, so they stopped being offered rather than being refused, and collapsing the two would misrepresent the evidence. Adds coverage that each retired id is excluded and that its documented replacement is still offered. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ --- packages/opencode/src/plugin/codex.ts | 24 ++++++++++- .../test/plugin/codex-allowlist.test.ts | 43 ++++++++++++++----- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 519dd3ae0..dd93080b2 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -111,11 +111,31 @@ const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 * the mechanism is a small drop-in: fetch, keep ``visibility === "list"``, and * fall back to this set whenever the response is empty or the call fails. * + * RETIRED — ``gpt-5.4`` and ``gpt-5.4-mini`` are excluded as of + * 2026-08-31T19:00:00Z. + * ``openai/codex``'s own shipped catalog + * (codex-rs/models-manager/models.json) marks both ``visibility: "hide"`` with + * ``upgrade.retirement_at: "2026-08-31T19:00:00Z"`` and the migration text + * "GPT-5.4 is no longer available. Codex now uses GPT-5.6 Terra in place of + * GPT-5.4." The documented replacements — ``gpt-5.4`` -> ``gpt-5.6-terra``, + * ``gpt-5.4-mini`` -> ``gpt-5.6-luna`` — are both already in this set, so + * affected users have a working model without further change. + * + * This is a retirement from the ChatGPT-subscription picker, NOT an API + * deprecation: both ids still carry ``supported_in_api: true``, neither is on + * OpenAI's deprecations page, and models.dev does not mark either + * ``deprecated``. API-key auth is unaffected and follows a longer schedule — + * this filter only runs when ``auth.type === "oauth"`` (see the models loader + * below), so key-authenticated users keep both ids. + * + * Because models.dev keeps serving these entries (they are still live API + * models), leaving them here would NOT self-heal after the deadline: they + * would stay in the subscription picker and fail at request time with the same + * opaque 400 this allowlist exists to prevent. + * * Exported for unit-test coverage — see test/plugin/codex-allowlist.test.ts. */ export const OAUTH_ALLOWED_MODELS = new Set([ "gpt-5.3-codex-spark", - "gpt-5.4", - "gpt-5.4-mini", "gpt-5.5", "gpt-5.6-luna", "gpt-5.6-sol", diff --git a/packages/opencode/test/plugin/codex-allowlist.test.ts b/packages/opencode/test/plugin/codex-allowlist.test.ts index f5f477bd4..d6f76a124 100644 --- a/packages/opencode/test/plugin/codex-allowlist.test.ts +++ b/packages/opencode/test/plugin/codex-allowlist.test.ts @@ -46,15 +46,20 @@ import { describe, expect, test } from "bun:test" import { OAUTH_ALLOWED_MODELS, disallowedOAuthModelKeys, shouldAllowOAuthModel } from "../../src/plugin/codex" /** Verified HTTP 200 on a ChatGPT Pro subscription credential. */ -const VERIFIED_ACCEPTED = [ - "gpt-5.3-codex-spark", - "gpt-5.4", - "gpt-5.4-mini", - "gpt-5.5", - "gpt-5.6-luna", - "gpt-5.6-sol", - "gpt-5.6-terra", -] +const VERIFIED_ACCEPTED = ["gpt-5.3-codex-spark", "gpt-5.5", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"] + +/** Retired from the ChatGPT-subscription picker on 2026-08-31T19:00:00Z. + * + * Both probed HTTP 200 before the deadline, so they are not in + * VERIFIED_REJECTED — they stopped being offered rather than being refused. + * Source: `openai/codex`'s shipped codex-rs/models-manager/models.json marks + * both `visibility: "hide"` with `upgrade.retirement_at: "2026-08-31T19:00:00Z"`. + * Replacements (gpt-5.4 -> gpt-5.6-terra, gpt-5.4-mini -> gpt-5.6-luna) are in + * VERIFIED_ACCEPTED above. + * + * These are still live API models, so models.dev keeps them and the catalog + * will not drop them for us — the allowlist is what has to. */ +const RETIRED_FROM_SUBSCRIPTION = ["gpt-5.4", "gpt-5.4-mini"] /** Verified HTTP 400 "not supported when using Codex with a ChatGPT account". */ const VERIFIED_REJECTED = [ @@ -94,6 +99,23 @@ describe("OAUTH_ALLOWED_MODELS — verified subscription truth table", () => { // against the live endpoint first and land it in VERIFIED_ACCEPTED too. expect([...OAUTH_ALLOWED_MODELS].sort()).toEqual([...VERIFIED_ACCEPTED].sort()) }) + + test("ids retired from the subscription picker are not offered", () => { + // gpt-5.4 / gpt-5.4-mini retired 2026-08-31T19:00:00Z. They remain live API + // models, so models.dev still lists them and the catalog will not remove + // them for us — if they were still allowlisted they would sit in the + // subscription picker and fail at request time. + for (const id of RETIRED_FROM_SUBSCRIPTION) { + expect(OAUTH_ALLOWED_MODELS.has(id)).toBe(false) + expect(shouldAllowOAuthModel(id)).toBe(false) + } + }) + + test("each retired id's documented replacement is offered", () => { + // The point of removing them is that users land somewhere that works. + expect(shouldAllowOAuthModel("gpt-5.6-terra")).toBe(true) // replaces gpt-5.4 + expect(shouldAllowOAuthModel("gpt-5.6-luna")).toBe(true) // replaces gpt-5.4-mini + }) }) describe("shouldAllowOAuthModel — behavior of the filter itself", () => { @@ -177,10 +199,11 @@ describe("disallowedOAuthModelKeys — what the loader actually deletes", () => const models = { "gpt-5.6-sol": model(undefined), "gpt-5.6": model(undefined), + // gpt-5.4 is retired from the subscription picker, so it is deleted too. "gpt-5.4": { api: {} }, "gpt-5.2": { api: {} }, } - expect(disallowedOAuthModelKeys(models).sort()).toEqual(["gpt-5.2", "gpt-5.6"]) + expect(disallowedOAuthModelKeys(models).sort()).toEqual(["gpt-5.2", "gpt-5.4", "gpt-5.6"]) }) test("the catalog set resolves identically whether matched by key or api.id", () => { From a82639ca980212b6ee347fad4875384fd20b1601 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Sat, 29 Aug 2026 17:52:57 -0700 Subject: [PATCH 2/4] docs: record why this removal is sequenced behind the catalog fix Review caught a case I had not checked. The filter only ever deletes, so it cannot add a model the catalog lacks. Release binaries built before the release.yml MODELS_DEV_API_JSON fix (#1186/#1188) embed a 2026-03-30 fixture that contains neither gpt-5.6-terra nor gpt-5.6-luna, so on a cold cache this removal takes an OAuth user from three selectable models to one: shipped snapshot, before: gpt-5.3-codex-spark, gpt-5.4, gpt-5.4-mini shipped snapshot, after: gpt-5.3-codex-spark live catalog, after: gpt-5.3-codex-spark, gpt-5.5, sol, luna, terra Released after #1188 the replacements are present and the regression does not occur. Comment-only; records the ordering constraint where the next reader of this allowlist will find it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ --- packages/opencode/src/plugin/codex.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index dd93080b2..a8da1d99a 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -119,7 +119,13 @@ const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 * "GPT-5.4 is no longer available. Codex now uses GPT-5.6 Terra in place of * GPT-5.4." The documented replacements — ``gpt-5.4`` -> ``gpt-5.6-terra``, * ``gpt-5.4-mini`` -> ``gpt-5.6-luna`` — are both already in this set, so - * affected users have a working model without further change. + * affected users have a working model without further change **provided their + * catalog is current**. This filter only ever deletes; it cannot add a model + * the catalog lacks. Release binaries built before the ``release.yml`` + * MODELS_DEV_API_JSON fix (#1186/#1188) embed a 2026-03-30 fixture that + * contains neither replacement, so on a cold cache this removal leaves only + * ``gpt-5.3-codex-spark`` selectable. That is why this change is sequenced + * behind that fix rather than shipped on its own. * * This is a retirement from the ChatGPT-subscription picker, NOT an API * deprecation: both ids still carry ``supported_in_api: true``, neither is on From f640a146d7f021e9103bf91cd624752319d79368 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Sat, 29 Aug 2026 18:19:05 -0700 Subject: [PATCH 3/4] docs: state the cold-cache model counts per artefact, not as one number Review correctly flagged the previous note as imprecise. It said the removal leaves "only gpt-5.3-codex-spark", which is true of shipped release binaries but not of a source checkout: the committed models-snapshot.ts blob is newer than the release.yml fixture and does carry gpt-5.5. Measured against all three catalogs: release fixture (105 providers): 3 allowed -> 1 (spark) committed blob (144 providers): 4 allowed -> 2 (spark, gpt-5.5) live models.dev (207 providers): 7 allowed -> 5 (spark, 5.5, sol/luna/terra) The sequencing rationale is unchanged and holds on either reading: no pre-#1188 bundled catalog contains gpt-5.6-terra or gpt-5.6-luna, so the user loses models with no documented replacement to move to until #1188 ships. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ --- packages/opencode/src/plugin/codex.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index a8da1d99a..fe6bd1ae2 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -121,11 +121,18 @@ const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 * ``gpt-5.4-mini`` -> ``gpt-5.6-luna`` — are both already in this set, so * affected users have a working model without further change **provided their * catalog is current**. This filter only ever deletes; it cannot add a model - * the catalog lacks. Release binaries built before the ``release.yml`` - * MODELS_DEV_API_JSON fix (#1186/#1188) embed a 2026-03-30 fixture that - * contains neither replacement, so on a cold cache this removal leaves only - * ``gpt-5.3-codex-spark`` selectable. That is why this change is sequenced - * behind that fix rather than shipped on its own. + * the catalog lacks, and NEITHER replacement is present in any pre-#1188 + * bundled catalog. On a cold cache this removal therefore leaves: + * + * - shipped release binaries (embed the 2026-03-30 ``release.yml`` fixture — + * see #1186/#1188): ``gpt-5.3-codex-spark`` only, down from three. + * - running from source (the committed ``models-snapshot.ts`` blob, which is + * newer): ``gpt-5.3-codex-spark`` and ``gpt-5.5``, down from four. + * + * Either way the user loses models with no gpt-5.6 replacement to move to, + * which is why this change is sequenced behind #1188 rather than shipped on its + * own. Once release binaries embed a release-time catalog the replacements are + * present and the gap closes. * * This is a retirement from the ChatGPT-subscription picker, NOT an API * deprecation: both ids still carry ``supported_in_api: true``, neither is on From a21f6faecbd64b89c6c9218604f03eb2e7b7a043 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Sat, 29 Aug 2026 19:54:17 -0700 Subject: [PATCH 4/4] docs: record the post-#1188 model counts now that the catalog fix has merged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1188 merged as `5993471bad`, so `release.yml` no longer pins `MODELS_DEV_API_JSON` to the 2026-03-30 test fixture and release builds embed a release-time models.dev catalog. The cold-cache figures in this comment were measured against pre-#1188 catalogs and read in the present tense, so they now describe a state that no longer exists. Re-measured by running the unmodified release build path (`MODELS_DEV_API_JSON` unset, which makes `strictCatalog` true) and inspecting the `models-snapshot.ts` it generates: 207 providers, 47 openai models, both `gpt-5.6-terra` and `gpt-5.6-luna` present. Applying the real `disallowedOAuthModelKeys` filter to that catalog, a subscription user goes from seven selectable models to five, losing only the two retired ids. The pre-#1188 wording is kept in the past tense because it is the reason the sequencing existed. The source-checkout figure (four down to two, off the committed `models-snapshot.ts` blob) is unchanged and re-verified. Comment only — no behaviour change. --- packages/opencode/src/plugin/codex.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index fe6bd1ae2..5daf5a5f9 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -121,18 +121,24 @@ const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 * ``gpt-5.4-mini`` -> ``gpt-5.6-luna`` — are both already in this set, so * affected users have a working model without further change **provided their * catalog is current**. This filter only ever deletes; it cannot add a model - * the catalog lacks, and NEITHER replacement is present in any pre-#1188 - * bundled catalog. On a cold cache this removal therefore leaves: + * the catalog lacks, and NEITHER replacement was present in any pre-#1188 + * bundled catalog. That is why this change was sequenced behind #1188 rather + * than shipped on its own: on a cold cache it would have left shipped release + * binaries (which embedded the 2026-03-30 ``release.yml`` fixture) with + * ``gpt-5.3-codex-spark`` alone, down from three. * - * - shipped release binaries (embed the 2026-03-30 ``release.yml`` fixture — - * see #1186/#1188): ``gpt-5.3-codex-spark`` only, down from three. - * - running from source (the committed ``models-snapshot.ts`` blob, which is - * newer): ``gpt-5.3-codex-spark`` and ``gpt-5.5``, down from four. + * #1188 has since merged (``5993471bad``), so release builds now embed a + * release-time models.dev catalog. Measured against one generated by the + * post-#1188 build path: 207 providers, 47 openai models, both ``gpt-5.6-terra`` + * and ``gpt-5.6-luna`` present. A subscription user on such a build goes from + * seven selectable models to five here, losing only the two retired ids and + * keeping every gpt-5.6 replacement. The gap this comment used to describe is + * closed. * - * Either way the user loses models with no gpt-5.6 replacement to move to, - * which is why this change is sequenced behind #1188 rather than shipped on its - * own. Once release binaries embed a release-time catalog the replacements are - * present and the gap closes. + * A source checkout is still served by the older committed + * ``models-snapshot.ts`` blob, where the same removal is four models down to two + * (``gpt-5.3-codex-spark`` and ``gpt-5.5``); that blob carries no gpt-5.6 entry + * and is refreshed by the next release build. * * This is a retirement from the ChatGPT-subscription picker, NOT an API * deprecation: both ids still carry ``supported_in_api: true``, neither is on