# NousResearch/hermes-agent · Codex provider: gpt-5.3-codex-spark model availability and selection

## Open now

- 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.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #22991. Verify it addresses your case.
- **Recommended:** read PR #22991 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-22991`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member 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.

## Closed / duplicate issues

- #16172: \[Bug\]: Cannot select gpt-5.3-codex-spark in openai-codex provider

## Prior & competing PRs

- **#22991: fix(codex): restore gpt-5.3-codex-spark for ChatGPT Pro (salvage #18286 + #19530, fixes #16172)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - 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`
  - fix hunks:
  - `agent/model_metadata.py`

    ```diff
    @@ -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,
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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",
    ```

    ```diff
    @@ -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")),
     ]
     
     
    ```

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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",
         "CREWorx@users.noreply.github.com": "BadTechBandit",
         "[redacted:email]": "Yoimex",
    ```
  - `tests/agent/test_model_metadata.py`

    ```diff
    @@ -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):
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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
     
     
    ```

    ```diff
    @@ -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"},
    ```

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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):
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```
- **#18286: feat(codex): add gpt-5.3-codex-spark model**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `44cdf555a`, `dcc8de83a`, `2a6f3deb5`, `c6dc295a3`, `#22991`, `#19530`, `#12994`, `#16172`
- **#19530: fix: surface Codex CLI-only models**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `44cdf555a`, `#22991`, `#18286`, `#12994`, `#16172`

## Fix status

- Merged to main in PR #22991 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-10`.
