# NousResearch/hermes-agent · OpenAI Codex auth state cleanup on logout

## Open now

- Nothing open on GitHub.

These items concern a bug where logging out of OpenAI Codex can leave stale provider state pinned, and related fixes to clear that state on logout.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #15061. Verify it addresses your case.
- **Recommended:** read PR #15061 (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-15061`. 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.67`: 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

- #15013: \[Bug\]: OpenAI Codex logout can leave Hermes pinned to openai-codex when auth state is already cleared

## Prior & competing PRs

- **#15061: fix(auth): clear stale OpenAI Codex provider state on logout (salvage #15018)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/auth.py`, `scripts/release.py`, `tests/hermes_cli/test_auth_commands.py`
  - fix hunks:
  - `hermes_cli/auth.py`

    ```diff
    @@ -955,10 +955,12 @@ def clear_provider_auth(provider_id: Optional[str] = None) -> bool:
                 del pool[target]
                 cleared = True
     
    -        if not cleared:
    -            return False
             if auth_store.get("active_provider") == target:
                 auth_store["active_provider"] = None
    +            cleared = True
    +
    +        if not cleared:
    +            return False
             _save_auth_store(auth_store)
         return True
     
    ```

    ```diff
    @@ -2826,6 +2828,46 @@ def _update_config_for_provider(
         return config_path
     
     
    +def _get_config_provider() -> Optional[str]:
    +    """Return model.provider from config.yaml, normalized, if present."""
    +    try:
    +        config = read_raw_config()
    +    except Exception:
    +        return None
    +    if not config:
    +        return None
    +    model = config.get("model")
    +    if not isinstance(model, dict):
    +        return None
    +    provider = model.get("provider")
    +    if not isinstance(provider, str):
    +        return None
    +    provider = provider.strip().lower()
    +    return provider or None
    +
    +
    +def _config_provider_matches(provider_id: Optional[str]) -> bool:
    +    """Return True when config.yaml currently selects *provider_id*."""
    +    if not provider_id:
    +        return False
    +    return _get_config_provider() == provider_id.strip().lower()
    +
    +
    +def _logout_default_provider_from_config() -> Optional[str]:
    +    """Fallback logout target when auth.json has no active provider.
    +
    +    `hermes logout` historically keyed off auth.json.active_provider only.
    +    That left users stuck when auth state had already been cleared but
    +    config.yaml still selected an OAuth provider such as openai-codex for the
    +    agent model: there was no active auth provider to target, so logout printed
    +    "No provider is currently logged in" and never reset model.provider.
    +    """
    +    provider = _get_config_provider()
    +    if provider in {"nous", "openai-codex"}:
    +        return provider
    +    return None
    +
    +
     def _reset_config_provider() -> Path:
         """Reset config.yaml provider back to auto after logout."""
         config_path = get_config_path()
    ```

    ```diff
    @@ -3524,15 +3566,16 @@ def logout_command(args) -> None:
             raise SystemExit(1)
     
         active = get_active_provider()
    -    target = provider_id or active
    +    target = provider_id or active or _logout_default_provider_from_config()
     
         if not target:
             print("No provider is currently logged in.")
             return
     
         provider_name = PROVIDER_REGISTRY[target].name if target in PROVIDER_REGISTRY else target
    +    config_matches = _config_provider_matches(target)
     
    -    if clear_provider_auth(target):
    +    if clear_provider_auth(target) or config_matches:
             _reset_config_provider()
             print(f"Logged out of {provider_name}.")
             if os.getenv("OPENROUTER_API_KEY"):
    ```
  - `scripts/release.py`

    ```diff
    @@ -189,6 +189,7 @@
         "[redacted:email]": "christomitov",
         "[redacted:email]": "NousResearch",
         "[redacted:email]": "ReginaldasR",
    +    "[redacted:email]": "0xharryriddle",
         "[redacted:email]": "benbarclay",
         "[redacted:email]": "ChimingLiu",
         "[redacted:email]": "openclaw",
    ```
  - `tests/hermes_cli/test_auth_commands.py`

    ```diff
    @@ -504,6 +504,91 @@ def test_clear_provider_auth_removes_provider_pool_entries(tmp_path, monkeypatch
         assert "openrouter" in payload.get("credential_pool", {})
     
     
    +def test_logout_resets_codex_config_when_auth_state_already_cleared(tmp_path, monkeypatch, capsys):
    +    """`hermes logout --provider openai-codex` must still clear model.provider.
    +
    +    Users can end up with auth.json already cleared but config.yaml still set to
    +    openai-codex.  Previously logout reported no auth state and left the agent
    +    pinned to the Codex provider.
    +    """
    +    hermes_home = tmp_path / "hermes"
    +    monkeypatch.setenv("HERMES_HOME", str(hermes_home))
    +    _write_auth_store(tmp_path, {"version": 1, "providers": {}, "credential_pool": {}})
    +    (hermes_home / "config.yaml").write_text(
    +        "model:\n"
    +        "  default: gpt-5.3-codex\n"
    +        "  provider: openai-codex\n"
    +        "  base_url: https://chatgpt.com/backend-api/codex\n"
    +    )
    +
    +    from types import SimpleNamespace
    +    from hermes_cli.auth import logout_command
    +
    +    logout_command(SimpleNamespace(provider="openai-codex"))
    +
    +    out = capsys.readouterr().out
    +    assert "Logged out of OpenAI Codex." in out
    +    config_text = (hermes_home / "config.yaml").read_text()
    +    assert "provider: auto" in config_text
    +    assert "base_url: https://openrouter.ai/api/v1" in config_text
    +
    +
    +def test_logout_defaults_to_configured_codex_when_no_active_provider(tmp_path, monkeypatch, capsys):
    +    """Bare `hermes logout` should target configured Codex if auth has no active provider."""
    +    hermes_home = tmp_path / "hermes"
    +    monkeypatch.setenv("HERMES_HOME", str(hermes_home))
    +    _write_auth_store(tmp_path, {"version": 1, "providers": {}, "credential_pool": {}})
    +    (hermes_home / "config.yaml").write_text(
    +        "model:\n"
    +        "  default: gpt-5.3-codex\n"
    +        "  provider: openai-codex\n"
    +        "  base_url: https://chatgpt.com/backend-api/codex\n"
    +    )
    +
    +    from types import SimpleNamespace
    +    from hermes_cli.auth import logout_command
    +
    +    logout_command(SimpleNamespace(provider=None))
    +
    +    out = capsys.readouterr().out
    +    assert "Logged out of OpenAI Codex." in out
    +    config_text = (hermes_home / "config.yaml").read_text()
    +    assert "provider: auto" in config_text
    +
    +
    +def test_logout_clears_stale_active_codex_without_provider_credentials(tmp_path, monkeypatch, capsys):
    +    """Logout must clear active_provider even when provider credential payloads are gone."""
    +    hermes_home = tmp_path / "hermes"
    +    monkeypatch.setenv("HERMES_HOME", str(hermes_home))
    +    _write_auth_store(
    +        tmp_path,
    +        {
    +            "version": 1,
    +            "active_provider": "openai-codex",
    +            "providers": {},
    +            "credential_pool": {},
    +        },
    +    )
    +    (hermes_home / "config.yaml").write_text(
    +        "model:\n"
    +        "  default: gpt-5.3-codex\n"
    +        "  provider: openai-codex\n"
    +        "  base_url: https://chatgpt.com/backend-api/codex\n"
    +    )
    +
    +    from types import SimpleNamespace
    +    from hermes_cli.auth import logout_command
    +
    +    logout_command(SimpleNamespace(provider=None))
    +
    +    out = capsys.readouterr().out
    +    assert "Logged out of OpenAI Codex." in out
    +    auth_payload = json.loads((hermes_home / "auth.json").read_text())
    +    assert auth_payload.get("active_provider") is None
    +    config_text = (hermes_home / "config.yaml").read_text()
    +    assert "provider: auto" in config_text
    +
    +
     def test_auth_list_does_not_call_mutating_select(monkeypatch, capsys):
         from hermes_cli.auth_commands import auth_list_command
     
    ```
- **#15018: fix(auth): clear stale OpenAI Codex provider state on logout**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#15061`

## Fix status

- Merged to main in PR #15061 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-24`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
