NousResearch/hermes-agent

Codex provider: gpt-5.3-codex-spark model availability and selection

Cluster 16172. Data as of

Nothing open on GitHub. Fix merged in #22991 on 10 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #22991. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #22991. That is separate from the fix event.
Still open
Nothing open on GitHub.

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.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #22991 fix(codex): restore gpt-5.3-codex-spark for ChatGPT Pro (salvage #18286 + #19530, fixes #16172)

    merged to main

    files: 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

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

2 PRs closed without merging

From the close comments: merged via another change 2

  • #18286 feat(codex): add gpt-5.3-codex-spark model · merged via another change → 44cdf55, dcc8de8, 2a6f3de, c6dc295, #22991, #19530, #12994, #16172
  • #19530 fix: surface Codex CLI-only models · merged via another change → 44cdf55, #22991, #18286, #12994, #16172
Closed issues · 1
  • Closed#16172[Bug]: Cannot select gpt-5.3-codex-spark in openai-codex provider
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#22991
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
10 May 2026

For your agent

The same facts, typed.