diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 52bdf646fbc93..80a57a361ff2f 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1715,11 +1715,29 @@ def write_credential_pool( def suppress_credential_source(provider_id: str, source: str) -> None: - """Mark a credential source as suppressed so it won't be re-seeded.""" + """Mark a credential source as suppressed so it won't be re-seeded. + + Older auth stores may represent a provider's suppressed sources as a + mapping. Treat its keys as source names and migrate the value to the + canonical list form before appending the requested source. + """ with _auth_store_lock(): auth_store = _load_auth_store() - suppressed = auth_store.setdefault("suppressed_sources", {}) - provider_list = suppressed.setdefault(provider_id, []) + suppressed = auth_store.get("suppressed_sources") + if not isinstance(suppressed, dict): + suppressed = {} + auth_store["suppressed_sources"] = suppressed + + raw_sources = suppressed.get(provider_id) + if isinstance(raw_sources, list): + provider_list = raw_sources + elif isinstance(raw_sources, dict): + provider_list = [str(name) for name in raw_sources] + suppressed[provider_id] = provider_list + else: + provider_list = [] + suppressed[provider_id] = provider_list + if source not in provider_list: provider_list.append(source) _save_auth_store(auth_store) @@ -1745,8 +1763,15 @@ def unsuppress_credential_source(provider_id: str, source: str) -> bool: suppressed = auth_store.get("suppressed_sources") if not isinstance(suppressed, dict): return False - provider_list = suppressed.get(provider_id) - if not isinstance(provider_list, list) or source not in provider_list: + raw_sources = suppressed.get(provider_id) + if isinstance(raw_sources, dict): + provider_list = [str(name) for name in raw_sources] + suppressed[provider_id] = provider_list + elif isinstance(raw_sources, list): + provider_list = raw_sources + else: + return False + if source not in provider_list: return False provider_list.remove(source) if not provider_list: diff --git a/tests/agent/test_anthropic_output_field_leak.py b/tests/agent/test_anthropic_output_field_leak.py index 93c05c3e62299..138aba2c0ef38 100644 --- a/tests/agent/test_anthropic_output_field_leak.py +++ b/tests/agent/test_anthropic_output_field_leak.py @@ -10,9 +10,6 @@ normalize_response capture, _sanitize_replay_block (ordered-blocks replay), and _convert_content_part_to_anthropic (content-list replay). """ -import sys, os -sys.path.insert(0, os.path.expanduser("~/.hermes/hermes-agent")) - import pytest from agent.anthropic_adapter import ( _sanitize_replay_block, diff --git a/tests/hermes_cli/test_auth_commands.py b/tests/hermes_cli/test_auth_commands.py index 3da85849c0448..f0a1010fb72a5 100644 --- a/tests/hermes_cli/test_auth_commands.py +++ b/tests/hermes_cli/test_auth_commands.py @@ -521,6 +521,40 @@ class _Args: assert entries[0]["priority"] == 0 +def test_auth_remove_codex_migrates_legacy_dict_suppression(tmp_path, monkeypatch): + """Removing a Codex credential must tolerate legacy dict suppression data.""" + monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) + store = _codex_pool_only_store() + primary = store["credential_pool"]["openai-codex"][0] + primary.update({"id": "codex-qb", "label": "qb"}) + store["suppressed_sources"] = {"openai-codex": {"legacy": True}} + _write_auth_store(tmp_path, store) + + from hermes_cli.auth_commands import auth_remove_command + + class _Args: + provider = "openai-codex" + target = "qb" + + auth_remove_command(_Args()) + + payload = json.loads((tmp_path / "hermes" / "auth.json").read_text(encoding="utf-8")) + assert payload.get("credential_pool", {}).get("openai-codex", []) == [] + assert payload["suppressed_sources"]["openai-codex"] == [ + "legacy", + "device_code", + "manual:device_code", + ] + + from hermes_cli.auth import unsuppress_credential_source + + assert unsuppress_credential_source("openai-codex", "legacy") is True + + from agent.credential_pool import load_pool + + assert load_pool("openai-codex").peek() is None + + def test_clear_provider_auth_removes_provider_pool_entries(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) _write_auth_store(