{"repo":"NousResearch/hermes-agent","public_id":"16172","display_label":"Codex provider: gpt-5.3-codex-spark model availability and selection","summary":"The members concern making the gpt-5.3-codex-spark model (and, more broadly, Codex CLI-only models) available or selectable in the Codex/openai-codex provider, including for ChatGPT Pro. They include a bug report about being unable to select the model and several pull requests that add or restore it; their outcomes are not stated in the titles.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22991,"merged_at":"2026-05-10T06:17:25+00:00"},"resolution":{"state":"resolved","by":"pr-22991"},"freshness":{"state":"verified","last_verified_at":"2026-05-10T06:17:25+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":16172,"pr_number":22991,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-10T06:17:25+00:00"}],"members":[{"kind":"issue","number":16172,"title":"[Bug]: Cannot select gpt-5.3-codex-spark in openai-codex provider","url":null,"state":"closed"},{"kind":"pr","number":18286,"title":"feat(codex): add gpt-5.3-codex-spark model","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"44cdf555a","number":0},{"kind":"commit","value":"dcc8de83a","number":0},{"kind":"commit","value":"2a6f3deb5","number":0},{"kind":"commit","value":"c6dc295a3","number":0},{"kind":"pr","value":"#22991","number":22991},{"kind":"pr","value":"#19530","number":19530},{"kind":"pr","value":"#12994","number":12994},{"kind":"pr","value":"#16172","number":16172}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":19530,"title":"fix: surface Codex CLI-only models","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"44cdf555a","number":0},{"kind":"pr","value":"#22991","number":22991},{"kind":"pr","value":"#18286","number":18286},{"kind":"pr","value":"#12994","number":12994},{"kind":"pr","value":"#16172","number":16172}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22991,"title":"fix(codex): restore gpt-5.3-codex-spark for ChatGPT Pro (salvage #18286 + #19530, fixes #16172)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","hermes_cli/codex_models.py","hermes_cli/model_switch.py","scripts/release.py","tests/agent/test_model_metadata.py","tests/hermes_cli/test_codex_cli_model_picker.py","tests/hermes_cli/test_codex_models.py","tests/hermes_cli/test_openai_codex_model_validation_fallback.py"],"fix_hunks":[{"file":"agent/model_metadata.py","hunks":["@@ -157,6 +157,13 @@ def _strip_provider_prefix(model: str) -> str:\n     \"gpt-5.4-nano\": 400000,           # 400k (not 1.05M like full 5.4)\n     \"gpt-5.4-mini\": 400000,           # 400k (not 1.05M like full 5.4)\n     \"gpt-5.4\": 1050000,               # GPT-5.4, GPT-5.4 Pro (1.05M context)\n+    # gpt-5.3-codex-spark is Codex-OAuth-only (ChatGPT Pro entitlement) and\n+    # uses a smaller 128k window than other gpt-5.x slugs. Listed here as\n+    # a defensive override so the longest-substring fallback doesn't match\n+    # the generic \"gpt-5\" entry below (400k) and report the wrong limit if\n+    # Spark's context ever needs to be resolved through this path. Real\n+    # usage flows through _CODEX_OAUTH_CONTEXT_FALLBACK at line ~1113.\n+    \"gpt-5.3-codex-spark\": 128000,\n     \"gpt-5.1-chat\": 128000,           # Chat variant has 128k context\n     \"gpt-5\": 400000,                  # GPT-5.x base, mini, codex variants (400k)\n     \"gpt-4.1\": 1047576,","@@ -1108,6 +1115,12 @@ def _query_anthropic_context_length(model: str, base_url: str, api_key: str) ->\n     \"gpt-5.1-codex-max\": 272_000,\n     \"gpt-5.1-codex-mini\": 272_000,\n     \"gpt-5.3-codex\": 272_000,\n+    # Spark runs on specialised low-latency hardware and exposes a smaller\n+    # 128k window than other Codex OAuth slugs. Listed explicitly so the\n+    # longest-key-first fallback resolves it correctly — substring match\n+    # on \"gpt-5.3-codex\" otherwise wins and reports 272k. Availability is\n+    # gated by ChatGPT Pro entitlement on the Codex backend.\n+    \"gpt-5.3-codex-spark\": 128_000,\n     \"gpt-5.2-codex\": 272_000,\n     \"gpt-5.4-mini\": 272_000,\n     \"gpt-5.5\": 272_000,"]},{"file":"hermes_cli/codex_models.py","hunks":["@@ -16,6 +16,19 @@\n     \"gpt-5.4-mini\",\n     \"gpt-5.4\",\n     \"gpt-5.3-codex\",\n+    # gpt-5.3-codex-spark is in research preview and is exposed *only* via\n+    # the Codex CLI / OAuth backend (chatgpt.com/backend-api/codex/models)\n+    # for ChatGPT Pro subscribers. It is NOT available in the public OpenAI\n+    # API, so it intentionally stays out of the \"openai\" provider catalog\n+    # in hermes_cli/models.py — only the openai-codex (OAuth) provider\n+    # surfaces it. The Codex backend reports ``supported_in_api: false`` for\n+    # this slug; that flag describes API availability, not Codex backend\n+    # availability, so the fetch/cache code paths below intentionally do\n+    # not filter on it. PR #12994 removed this entry on the assumption it\n+    # was unsupported — that was wrong; restored here. Keep it in the\n+    # curated fallback so Pro users still see Spark in `/model` when live\n+    # discovery is unavailable (offline first run, transient API failure).\n+    \"gpt-5.3-codex-spark\",\n     \"gpt-5.2-codex\",\n     \"gpt-5.1-codex-max\",\n     \"gpt-5.1-codex-mini\",","@@ -26,6 +39,11 @@\n     (\"gpt-5.4-mini\", (\"gpt-5.3-codex\", \"gpt-5.2-codex\")),\n     (\"gpt-5.4\", (\"gpt-5.3-codex\", \"gpt-5.2-codex\")),\n     (\"gpt-5.3-codex\", (\"gpt-5.2-codex\",)),\n+    # Surface Spark whenever any compatible Codex template is present so\n+    # accounts hitting the live endpoint with an older lineup still see\n+    # Spark in the picker. Backend gates real availability by ChatGPT Pro\n+    # entitlement; Hermes does not.\n+    (\"gpt-5.3-codex-spark\", (\"gpt-5.3-codex\", \"gpt-5.2-codex\")),\n ]\n \n ","@@ -78,8 +96,10 @@ def _fetch_models_from_api(access_token: str) -> List[str]:\n         if not isinstance(slug, str) or not slug.strip():\n             continue\n         slug = slug.strip()\n-        if item.get(\"supported_in_api\") is False:\n-            continue\n+        # Codex CLI's catalog uses ``supported_in_api`` for the public OpenAI\n+        # API, not for the OAuth-backed Codex backend that this provider uses.\n+        # Some valid Codex CLI models (for example gpt-5.3-codex-spark) are\n+        # marked false here but are still accepted by the Codex route.\n         visibility = item.get(\"visibility\", \"\")\n         if isinstance(visibility, str) and visibility.strip().lower() in (\"hide\", \"hidden\"):\n             continue","@@ -128,8 +148,9 @@ def _read_cache_models(codex_home: Path) -> List[str]:\n             if not isinstance(slug, str) or not slug.strip():\n                 continue\n             slug = slug.strip()\n-            if item.get(\"supported_in_api\") is False:\n-                continue\n+            # Do not filter on ``supported_in_api`` here.  It describes the\n+            # public OpenAI API, while Hermes openai-codex talks to the same\n+            # OAuth-backed Codex backend as Codex CLI.\n             visibility = item.get(\"visibility\")\n             if isinstance(visibility, str) and visibility.strip().lower() in (\"hide\", \"hidden\"):\n                 continue"]},{"file":"hermes_cli/model_switch.py","hunks":["@@ -1342,7 +1342,14 @@ def _has_aws_sdk_creds_for_listing(slug: str) -> bool:\n         if not has_creds:\n             continue\n \n-        if hermes_slug in {\"copilot\", \"copilot-acp\"}:\n+        if hermes_slug in {\"openai-codex\", \"copilot\", \"copilot-acp\"}:\n+            # Use live OAuth-backed discovery so the gateway /model picker\n+            # matches what the user's authenticated Codex/Copilot backend\n+            # actually serves — including ChatGPT-Pro-only Codex slugs\n+            # (e.g. gpt-5.3-codex-spark) that aren't in the static curated\n+            # catalog. ``provider_model_ids()`` falls back to the curated\n+            # list when the live endpoint is unreachable, so this is safe\n+            # for unauthenticated and offline cases too.\n             model_ids = provider_model_ids(hermes_slug)\n         # For aws_sdk providers (bedrock), use live discovery so the list\n         # reflects the active region (eu.*, ap.*) not the static us.* list."]},{"file":"scripts/release.py","hunks":["@@ -235,6 +235,9 @@\n     \"[redacted:email]\": \"Ito-69\",\n     \"[redacted:email]\": \"georgeglessner\",\n     \"[redacted:email]\": \"maxims-oss\",\n+    # Codex Spark restoration salvage (May 2026)\n+    \"[redacted:email]\": \"nederev\",\n+    \"[redacted:email]\": \"askclaw-vesper\",\n     \"[redacted:email]\": \"nazirulhafiy\",\n     \"CREWorx@users.noreply.github.com\": \"BadTechBandit\",\n     \"[redacted:email]\": \"Yoimex\","]},{"file":"tests/agent/test_model_metadata.py","hunks":["@@ -262,8 +262,9 @@ def test_dict_is_not_empty(self):\n class TestCodexOAuthContextLength:\n     \"\"\"ChatGPT Codex OAuth imposes lower context limits than the direct\n     OpenAI API for the same slugs. Verified Apr 2026 via live probe of\n-    chatgpt.com/backend-api/codex/models: every model returns 272k, while\n+    chatgpt.com/backend-api/codex/models: most models return 272k, while\n     models.dev reports 1.05M for gpt-5.5/gpt-5.4 and 400k for the rest.\n+    (Known exception: gpt-5.3-codex-spark is 128k.)\n     \"\"\"\n \n     def setup_method(self):","@@ -277,25 +278,28 @@ def test_fallback_table_used_without_token(self):\n         \"\"\"\n         from agent.model_metadata import get_model_context_length\n \n+        expected = {\n+            \"gpt-5.5\": 272_000,\n+            \"gpt-5.4\": 272_000,\n+            \"gpt-5.4-mini\": 272_000,\n+            \"gpt-5.3-codex\": 272_000,\n+            \"gpt-5.3-codex-spark\": 128_000,\n+            \"gpt-5.2-codex\": 272_000,\n+            \"gpt-5.1-codex-max\": 272_000,\n+            \"gpt-5.1-codex-mini\": 272_000,\n+        }\n+\n         with patch(\"agent.model_metadata.get_cached_context_length\", return_value=None), \\\n              patch(\"agent.model_metadata.save_context_length\"):\n-            for model in (\n-                \"gpt-5.5\",\n-                \"gpt-5.4\",\n-                \"gpt-5.4-mini\",\n-                \"gpt-5.3-codex\",\n-                \"gpt-5.2-codex\",\n-                \"gpt-5.1-codex-max\",\n-                \"gpt-5.1-codex-mini\",\n-            ):\n+            for model, expected_ctx in expected.items():\n                 ctx = get_model_context_length(\n                     model=model,\n                     base_url=\"https://chatgpt.com/backend-api/codex\",\n                     api_key=\"\",\n                     provider=\"openai-codex\",\n                 )\n-                assert ctx == 272_000, (\n-                    f\"Codex {model}: expected 272000 fallback, got {ctx} \"\n+                assert ctx == expected_ctx, (\n+                    f\"Codex {model}: expected {expected_ctx} fallback, got {ctx} \"\n                     \"(models.dev leakage?)\"\n                 )\n "]},{"file":"tests/hermes_cli/test_codex_cli_model_picker.py","hunks":["@@ -75,6 +75,37 @@ def test_normal_path_still_works(hermes_auth_only_env):\n     assert \"openai-codex\" in slugs\n \n \n+def test_codex_picker_uses_live_codex_catalog(hermes_auth_only_env, tmp_path, monkeypatch):\n+    \"\"\"The gateway /model picker should surface Codex CLI-only listed models.\"\"\"\n+    from hermes_cli.model_switch import list_authenticated_providers\n+\n+    codex_home = tmp_path / \"codex-home\"\n+    codex_home.mkdir()\n+    (codex_home / \"models_cache.json\").write_text(json.dumps({\n+        \"models\": [\n+            {\"slug\": \"gpt-5.5\", \"priority\": 0, \"supported_in_api\": True},\n+            {\"slug\": \"gpt-5.3-codex-spark\", \"priority\": 7, \"supported_in_api\": False},\n+        ]\n+    }))\n+    monkeypatch.setenv(\"CODEX_HOME\", str(codex_home))\n+    # Force the cache fallback path — without this the test issues a real\n+    # 10s HTTP probe to chatgpt.com/backend-api/codex/models which is both\n+    # slow and non-deterministic in CI/sandboxed environments.\n+    monkeypatch.setattr(\n+        \"hermes_cli.codex_models._fetch_models_from_api\",\n+        lambda access_token: [],\n+    )\n+\n+    providers = list_authenticated_providers(\n+        current_provider=\"openai-codex\",\n+        max_models=10,\n+    )\n+\n+    codex = next(p for p in providers if p[\"slug\"] == \"openai-codex\")\n+    assert \"gpt-5.3-codex-spark\" in codex[\"models\"]\n+    assert codex[\"total_models\"] == len(codex[\"models\"])\n+\n+\n @pytest.fixture()\n def claude_code_only_env(tmp_path, monkeypatch):\n     \"\"\"Set up an environment where Anthropic credentials only exist in"]},{"file":"tests/hermes_cli/test_codex_models.py","hunks":["@@ -1,10 +1,6 @@\n import json\n-import os\n-import sys\n from unittest.mock import patch\n \n-sys.path.insert(0, os.path.join(os.path.dirname(__file__), \"..\"))\n-\n from hermes_cli.codex_models import DEFAULT_CODEX_MODELS, get_codex_model_ids\n \n ","@@ -17,6 +13,7 @@ def test_get_codex_model_ids_prioritizes_default_and_cache(tmp_path, monkeypatch\n             {\n                 \"models\": [\n                     {\"slug\": \"gpt-5.3-codex\", \"priority\": 20, \"supported_in_api\": True},\n+                    {\"slug\": \"gpt-5.3-codex-spark\", \"priority\": 6, \"supported_in_api\": False},\n                     {\"slug\": \"gpt-5.1-codex\", \"priority\": 5, \"supported_in_api\": True},\n                     {\"slug\": \"gpt-5.4\", \"priority\": 1, \"supported_in_api\": True},\n                     {\"slug\": \"gpt-5-hidden-codex\", \"priority\": 2, \"visibility\": \"hidden\"},","@@ -31,6 +28,9 @@ def test_get_codex_model_ids_prioritizes_default_and_cache(tmp_path, monkeypatch\n     assert models[0] == \"gpt-5.2-codex\"\n     assert \"gpt-5.1-codex\" in models\n     assert \"gpt-5.3-codex\" in models\n+    # Codex CLI marks Spark unsupported in the public API, but the Codex\n+    # backend still accepts it via the OAuth-backed CLI/Hermes route.\n+    assert \"gpt-5.3-codex-spark\" in models\n     # Non-codex-suffixed models are included when the cache says they're available\n     assert \"gpt-5.4\" in models\n     assert \"gpt-5.4-mini\" in models","@@ -54,7 +54,7 @@ def test_get_codex_model_ids_falls_back_to_curated_defaults(tmp_path, monkeypatc\n \n     assert models[: len(DEFAULT_CODEX_MODELS)] == DEFAULT_CODEX_MODELS\n     assert \"gpt-5.4\" in models\n-    assert \"gpt-5.3-codex-spark\" not in models\n+    assert \"gpt-5.3-codex-spark\" in models\n \n \n def test_get_codex_model_ids_adds_forward_compat_models_from_templates(monkeypatch):","@@ -65,7 +65,49 @@ def test_get_codex_model_ids_adds_forward_compat_models_from_templates(monkeypat\n \n     models = get_codex_model_ids(access_token=\"[redacted:secret-assignment]\")\n \n-    assert models == [\"gpt-5.2-codex\", \"gpt-5.4-mini\", \"gpt-5.4\", \"gpt-5.3-codex\"]\n+    assert models == [\n+        \"gpt-5.2-codex\",\n+        \"gpt-5.4-mini\",\n+        \"gpt-5.4\",\n+        \"gpt-5.3-codex\",\n+        \"gpt-5.3-codex-spark\",\n+    ]\n+\n+\n+def test_fetch_from_api_keeps_supported_in_api_false_models(monkeypatch):\n+    \"\"\"Regression: gpt-5.3-codex-spark is returned by the live Codex backend\n+    with ``supported_in_api: false`` because it isn't in the public OpenAI\n+    API. The Codex CLI / OAuth route still serves it for ChatGPT Pro\n+    accounts, so we must not drop it on that flag. visibility=hidden is\n+    the separate signal that *should* still filter entries out.\n+    \"\"\"\n+    import sys\n+    from hermes_cli import codex_models\n+\n+    class _FakeResp:\n+        status_code = 200\n+\n+        def json(self):\n+            return {\n+                \"models\": [\n+                    {\"slug\": \"gpt-5.5\", \"priority\": 0, \"supported_in_api\": True},\n+                    {\"slug\": \"gpt-5.3-codex-spark\", \"priority\": 7, \"supported_in_api\": False},\n+                    {\"slug\": \"gpt-5-internal\", \"priority\": 99, \"visibility\": \"hidden\"},\n+                ]\n+            }\n+\n+    class _FakeHttpx:\n+        @staticmethod\n+        def get(url, headers=None, timeout=None):\n+            return _FakeResp()\n+\n+    monkeypatch.setitem(sys.modules, \"httpx\", _FakeHttpx)\n+\n+    models = codex_models._fetch_models_from_api(access_token=\"tok\")\n+\n+    assert \"gpt-5.5\" in models\n+    assert \"gpt-5.3-codex-spark\" in models\n+    assert \"gpt-5-internal\" not in models\n \n \n def test_model_command_uses_runtime_access_token_for_codex_list(monkeypatch):"]},{"file":"tests/hermes_cli/test_openai_codex_model_validation_fallback.py","hunks":["@@ -1,9 +1,18 @@\n \"\"\"Regression tests for OpenAI Codex model validation when the listing lags behind\n actually usable backend model IDs.\n \n-The bug: `/model` and `switch_model()` reject `gpt-5.3-codex-spark` because the\n-OpenAI Codex listing omits it, even though direct runtime calls with\n-`--provider openai-codex -m gpt-5.3-codex-spark` succeed.\n+The bug originally reported in #16172: `/model` and `switch_model()` rejected\n+`gpt-5.3-codex-spark` because the curated listing omitted it, even though direct\n+runtime calls succeeded. PR #19729 fixed this by soft-accepting unknown-but-\n+plausible Codex slugs with a warning, and this test pins the soft-accept\n+behavior so it doesn't regress.\n+\n+Note: gpt-5.3-codex-spark itself is now in the curated catalog (PR #22991),\n+so the real-world Spark request takes the `recognized=True` fast path. This\n+test still uses Spark as the example slug but explicitly mocks\n+``provider_model_ids`` to omit it, exercising the soft-accept path generically\n+for any future entitlement-gated Codex slug that ships before Hermes catalogs\n+it.\n \"\"\"\n \n from unittest.mock import patch\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}