diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 98ac4edb31..28c5bd9a61 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -619,7 +619,25 @@ def _oauth_trace(event: str, *, sequence_id: Optional[str] = None, **fields: Any # ============================================================================= def _auth_file_path() -> Path: - return get_hermes_home() / "auth.json" + path = get_hermes_home() / "auth.json" + # Seat belt: if pytest is running and HERMES_HOME resolves to the real + # user's auth store, refuse rather than silently corrupt it. This catches + # tests that forgot to monkeypatch HERMES_HOME, tests invoked without the + # hermetic conftest, or sandbox escapes via threads/subprocesses. In + # production (no PYTEST_CURRENT_TEST) this is a single dict lookup. + if os.environ.get("PYTEST_CURRENT_TEST"): + real_home_auth = (Path.home() / ".hermes" / "auth.json").resolve(strict=False) + try: + resolved = path.resolve(strict=False) + except Exception: + resolved = path + if resolved == real_home_auth: + raise RuntimeError( + f"Refusing to touch real user auth store during test run: {path}. " + "Set HERMES_HOME to a tmp_path in your test fixture, or run " + "via scripts/run_tests.sh for hermetic CI-parity env." + ) + return path def _auth_lock_path() -> Path: diff --git a/tests/agent/test_credential_pool.py b/tests/agent/test_credential_pool.py index 7ec0385b60..76e1412bf4 100644 --- a/tests/agent/test_credential_pool.py +++ b/tests/agent/test_credential_pool.py @@ -333,66 +333,6 @@ def test_mark_exhausted_and_rotate_persists_status(tmp_path, monkeypatch): assert persisted["last_error_code"] == 402 -def test_try_refresh_current_updates_only_current_entry(tmp_path, monkeypatch): - monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) - _write_auth_store( - tmp_path, - { - "version": 1, - "credential_pool": { - "openai-codex": [ - { - "id": "cred-1", - "label": "primary", - "auth_type": "oauth", - "priority": 0, - "source": "device_code", - "access_token": "access-old", - "refresh_token": "refresh-old", - "base_url": "https://chatgpt.com/backend-api/codex", - }, - { - "id": "cred-2", - "label": "secondary", - "auth_type": "oauth", - "priority": 1, - "source": "device_code", - "access_token": "access-other", - "refresh_token": "refresh-other", - "base_url": "https://chatgpt.com/backend-api/codex", - }, - ] - }, - }, - ) - - from agent.credential_pool import load_pool - - monkeypatch.setattr( - "hermes_cli.auth.refresh_codex_oauth_pure", - lambda access_token, refresh_token, timeout_seconds=20.0: { - "access_token": "access-new", - "refresh_token": "refresh-new", - }, - ) - - pool = load_pool("openai-codex") - current = pool.select() - assert current.id == "cred-1" - - refreshed = pool.try_refresh_current() - - assert refreshed is not None - assert refreshed.access_token == "access-new" - - auth_payload = json.loads((tmp_path / "hermes" / "auth.json").read_text()) - primary, secondary = auth_payload["credential_pool"]["openai-codex"] - assert primary["access_token"] == "access-new" - assert primary["refresh_token"] == "refresh-new" - assert secondary["access_token"] == "access-other" - assert secondary["refresh_token"] == "refresh-other" - - def test_load_pool_seeds_env_api_key(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-seeded")