hermes - 💡(How to fix) Fix Test suite opens a real browser / xAI OAuth flow + reads the dev's macOS Keychain on local runs

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Running the test suite on a local (non-headless) machine has two hermeticity leaks that are invisible on CI:

  1. it pops a real browser window and starts a real xAI OAuth handshake;
  2. the anthropic token tests read the developer's real macOS Keychain and pick up the live sk-ant-oat… Claude Code token.

Both are green on CI (headless Linux, no Keychain, no Claude Code creds), so they only bite local scripts/run_tests.sh runs.

Error Message

@pytest.fixture(autouse=True) def _neutralize_webbrowser(monkeypatch): """No test should open a real browser. Stub webbrowser.open* to a no-op returning True; tests that assert on it install their own monkeypatch.""" import webbrowser as _wb opened: "list[object]" = [] def _record(url=None, *a, **k): opened.append(url); return True for _name in ("open", "open_new", "open_new_tab"): monkeypatch.setattr(_wb, _name, _record, raising=False) yield opened

@pytest.fixture(autouse=True) def _neutralize_macos_keychain_creds(monkeypatch): """resolve_anthropic_token() falls back to the macOS Keychain; the token tests isolate the file but not the Keychain, leaking the dev's real token. Default the Keychain read to None; the test that exercises it overrides.""" try: import agent.anthropic_adapter as _aa except Exception: yield; return monkeypatch.setattr(_aa, "_read_claude_code_credentials_from_keychain", lambda *a, **k: None, raising=False) yield

Root Cause

Repro: on a Mac with Claude Code logged in, scripts/run_tests.sh tests/agent/test_anthropic_adapter.py → ~14 failures (TestResolveAnthropicToken / TestResolveWithRefresh / TestRunOauthSetupToken); e.g. test_returns_none_with_no_creds fails because resolve_anthropic_token() returns a live sk-ant-oat… token.

Fix Action

Fix / Workaround

Cause: these tests isolate the credential file (monkeypatch Path.home → tmp_path) but not the Keychain. read_claude_code_credentials() falls back to _read_claude_code_credentials_from_keychain(), which reads the dev's live Keychain. CI (Linux, no Keychain) → None → green.

@pytest.fixture(autouse=True)
def _neutralize_webbrowser(monkeypatch):
    """No test should open a real browser. Stub webbrowser.open* to a no-op
    returning True; tests that assert on it install their own monkeypatch."""
    import webbrowser as _wb
    opened: "list[object]" = []
    def _record(url=None, *a, **k):
        opened.append(url); return True
    for _name in ("open", "open_new", "open_new_tab"):
        monkeypatch.setattr(_wb, _name, _record, raising=False)
    yield opened

@pytest.fixture(autouse=True)
def _neutralize_macos_keychain_creds(monkeypatch):
    """resolve_anthropic_token() falls back to the macOS Keychain; the token
    tests isolate the file but not the Keychain, leaking the dev's real token.
    Default the Keychain read to None; the test that exercises it overrides."""
    try:
        import agent.anthropic_adapter as _aa
    except Exception:
        yield; return
    monkeypatch.setattr(_aa, "_read_claude_code_credentials_from_keychain",
                        lambda *a, **k: None, raising=False)
    yield

Code Example

@pytest.fixture(autouse=True)
def _neutralize_webbrowser(monkeypatch):
    """No test should open a real browser. Stub webbrowser.open* to a no-op
    returning True; tests that assert on it install their own monkeypatch."""
    import webbrowser as _wb
    opened: "list[object]" = []
    def _record(url=None, *a, **k):
        opened.append(url); return True
    for _name in ("open", "open_new", "open_new_tab"):
        monkeypatch.setattr(_wb, _name, _record, raising=False)
    yield opened

@pytest.fixture(autouse=True)
def _neutralize_macos_keychain_creds(monkeypatch):
    """resolve_anthropic_token() falls back to the macOS Keychain; the token
    tests isolate the file but not the Keychain, leaking the dev's real token.
    Default the Keychain read to None; the test that exercises it overrides."""
    try:
        import agent.anthropic_adapter as _aa
    except Exception:
        yield; return
    monkeypatch.setattr(_aa, "_read_claude_code_credentials_from_keychain",
                        lambda *a, **k: None, raising=False)
    yield
RAW_BUFFERClick to expand / collapse

Summary

Running the test suite on a local (non-headless) machine has two hermeticity leaks that are invisible on CI:

  1. it pops a real browser window and starts a real xAI OAuth handshake;
  2. the anthropic token tests read the developer's real macOS Keychain and pick up the live sk-ant-oat… Claude Code token.

Both are green on CI (headless Linux, no Keychain, no Claude Code creds), so they only bite local scripts/run_tests.sh runs.

1. webbrowser.open opens a real browser / OAuth flow

Repro: scripts/run_tests.sh tests/hermes_cli/test_auth_manual_paste.py on a macOS/Linux desktop (not SSH/Codespaces) → a browser opens to https://auth.x.ai/oauth2/authorize?…redirect_uri=http://127.0.0.1:56121/callback.

Cause: test_xai_loopback_login_timeout_falls_back_to_manual_paste and test_xai_loopback_login_timeout_noninteractive_reraises call _xai_oauth_loopback_login(manual_paste=False) with the default open_browser=True. On a non-remote box _is_remote_session() is False, so hermes_cli/auth.py reaches webbrowser.open(authorize_url). tests/conftest.py::_live_system_guard neutralizes kill/systemctl/subprocess but not webbrowser.

Expected: no test ever opens a real browser or starts an OAuth handshake.

2. anthropic token tests read the real macOS Keychain

Repro: on a Mac with Claude Code logged in, scripts/run_tests.sh tests/agent/test_anthropic_adapter.py → ~14 failures (TestResolveAnthropicToken / TestResolveWithRefresh / TestRunOauthSetupToken); e.g. test_returns_none_with_no_creds fails because resolve_anthropic_token() returns a live sk-ant-oat… token.

Cause: these tests isolate the credential file (monkeypatch Path.home → tmp_path) but not the Keychain. read_claude_code_credentials() falls back to _read_claude_code_credentials_from_keychain(), which reads the dev's live Keychain. CI (Linux, no Keychain) → None → green.

Expected: the token tests don't read the developer's real Keychain.

Suggested fix (drop-in, no PR needed)

Two autouse fixtures in tests/conftest.py, siblings to _live_system_guard:

@pytest.fixture(autouse=True)
def _neutralize_webbrowser(monkeypatch):
    """No test should open a real browser. Stub webbrowser.open* to a no-op
    returning True; tests that assert on it install their own monkeypatch."""
    import webbrowser as _wb
    opened: "list[object]" = []
    def _record(url=None, *a, **k):
        opened.append(url); return True
    for _name in ("open", "open_new", "open_new_tab"):
        monkeypatch.setattr(_wb, _name, _record, raising=False)
    yield opened

@pytest.fixture(autouse=True)
def _neutralize_macos_keychain_creds(monkeypatch):
    """resolve_anthropic_token() falls back to the macOS Keychain; the token
    tests isolate the file but not the Keychain, leaking the dev's real token.
    Default the Keychain read to None; the test that exercises it overrides."""
    try:
        import agent.anthropic_adapter as _aa
    except Exception:
        yield; return
    monkeypatch.setattr(_aa, "_read_claude_code_credentials_from_keychain",
                        lambda *a, **k: None, raising=False)
    yield

Verified locally: with these, test_auth_manual_paste.py + test_anthropic_adapter.py go green with no browser popup and no Keychain access.

Environment

macOS 15 · Python 3.12 · Hermes 0.15.2

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix Test suite opens a real browser / xAI OAuth flow + reads the dev's macOS Keychain on local runs