Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions hermes_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
3 changes: 0 additions & 3 deletions tests/agent/test_anthropic_output_field_leak.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions tests/hermes_cli/test_auth_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down