# NousResearch/hermes-agent · Doctor suppressing stale direct-key issues with OAuth

## Open now

- Nothing open on GitHub.

These members address a bug where the doctor command surfaces invalid direct API key warnings even when OAuth is functioning correctly. The fix involves suppressing those stale direct-key issues when OAuth is healthy.

## 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 #26853. Verify it addresses your case.
- **Recommended:** read PR #26853 (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-26853`. 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

- #26693: \[Bug\]: hermes doctor promotes invalid direct API keys into the final summary even when OAuth fallback is already healthy

## Prior & competing PRs

- **#26853: fix(doctor): suppress stale direct-key issues when oauth is healthy (salvage of #26704)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/doctor.py`, `scripts/release.py`, `tests/hermes_cli/test_doctor.py`
  - fix hunks:
  - `hermes_cli/doctor.py`

    ```diff
    @@ -152,6 +152,30 @@ def _apply_doctor_tool_availability_overrides(available: list[str], unavailable:
         return updated_available, updated_unavailable
     
     
    +def _has_healthy_oauth_fallback_for_apikey_provider(provider_label: str) -> bool:
    +    """Return True when a direct API-key probe failure is non-blocking.
    +
    +    Some provider families support both a direct API-key path and a separate
    +    OAuth runtime path. When the OAuth path is already healthy, doctor should
    +    still show a failed API-key connectivity row, but it should not promote
    +    that direct-key problem into the final blocking summary.
    +    """
    +    try:
    +        from hermes_cli.auth import (
    +            get_gemini_oauth_auth_status,
    +            get_minimax_oauth_auth_status,
    +        )
    +    except Exception:
    +        return False
    +
    +    normalized = (provider_label or "").strip().lower()
    +    if normalized in {"google / gemini", "gemini"}:
    +        return bool((get_gemini_oauth_auth_status() or {}).get("logged_in"))
    +    if normalized == "minimax":
    +        return bool((get_minimax_oauth_auth_status() or {}).get("logged_in"))
    +    return False
    +
    +
     def check_ok(text: str, detail: str = ""):
         print(f"  {color('✓', Colors.GREEN)} {text}" + (f" {color(detail, Colors.DIM)}" if detail else ""))
     
    ```

    ```diff
    @@ -1594,7 +1618,10 @@ def _probe_bedrock() -> _ConnectivityResult:
                     print(f"  {_glyph} {_label} {_detail}")
                 else:
                     print(f"  {_glyph} {_label}")
    -        for _issue in _r.issues:
    +        _issues_to_add = list(_r.issues)
    +        if _issues_to_add and _has_healthy_oauth_fallback_for_apikey_provider(_r.label):
    +            _issues_to_add = []
    +        for _issue in _issues_to_add:
                 issues.append(_issue)
     
         # =========================================================================
    ```
  - `scripts/release.py`

    ```diff
    @@ -92,6 +92,7 @@
         "30397170+1000Delta@users.noreply.github.com": "1000Delta",
         "[redacted:email]": "szymonclawd",
         "257759490+szymonclawd@users.noreply.github.com": "szymonclawd",
    +    "101180447+worlldz@users.noreply.github.com": "worlldz",
         "[redacted:email]": "luoyuctl",
         "51604064+luoyuctl@users.noreply.github.com": "luoyuctl",
         "127238744+teknium1@users.noreply.github.com": "teknium1",
    ```
  - `tests/hermes_cli/test_doctor.py`

    ```diff
    @@ -839,3 +839,108 @@ def mock_run(cmd, **kwargs):
     
             assert "gh auth" in str(call_log) or any(c[0] == "gh" for c in call_log), f"gh not called: {call_log}"
             assert "GitHub authenticated via gh CLI" in out or "token configured" in out
    +
    +
    +def _run_doctor_with_healthy_oauth_fallback(
    +    monkeypatch,
    +    tmp_path,
    +    *,
    +    env_key: str,
    +    bad_key: str,
    +    failing_host: str,
    +    gemini_oauth_status: dict,
    +    minimax_oauth_status: dict,
    +) -> str:
    +    home = tmp_path / ".hermes"
    +    home.mkdir(parents=True, exist_ok=True)
    +    (home / "config.yaml").write_text(
    +        "model:\n"
    +        "  provider: nous\n"
    +        "  default: moonshotai/kimi-k2.6\n",
    +        encoding="utf-8",
    +    )
    +    project = tmp_path / "project"
    +    project.mkdir(exist_ok=True)
    +
    +    monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
    +    monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project)
    +    monkeypatch.setattr(doctor_mod, "_DHH", str(home))
    +    monkeypatch.setenv(env_key, bad_key)
    +    monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
    +    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    +    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    +    monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
    +    monkeypatch.delenv("MINIMAX_API_KEY", raising=False)
    +    monkeypatch.delenv("MINIMAX_CN_API_KEY", raising=False)
    +    monkeypatch.setenv(env_key, bad_key)
    +
    +    fake_model_tools = types.SimpleNamespace(
    +        check_tool_availability=lambda *a, **kw: ([], []),
    +        TOOLSET_REQUIREMENTS={},
    +    )
    +    monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)
    +
    +    from hermes_cli import auth as _auth_mod
    +
    +    monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {"logged_in": True})
    +    monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {})
    +    monkeypatch.setattr(_auth_mod, "get_gemini_oauth_auth_status", lambda: gemini_oauth_status)
    +    monkeypatch.setattr(_auth_mod, "get_minimax_oauth_auth_status", lambda: minimax_oauth_status)
    +
    +    def fake_get(url, headers=None, timeout=None):
    +        status = 401 if failing_host in url else 200
    +        return types.SimpleNamespace(status_code=status)
    +
    +    import httpx
    +
    +    monkeypatch.setattr(httpx, "get", fake_get)
    +
    +    buf = io.StringIO()
    +    with contextlib.redirect_stdout(buf):
    +        doctor_mod.run_doctor(Namespace(fix=False))
    +    return buf.getvalue()
    +
    +
    [redacted:email](
    +    ("env_key", "bad_key", "failing_host", "gemini_oauth_status", "minimax_oauth_status", "unexpected_issue"),
    +    [
    +        (
    +            "GOOGLE_API_KEY",
    +            "bad-gemini-key",
    +            "googleapis.com",
    +            {"logged_in": True, "email": "[redacted:email]"},
    +            {},
    +            "Check GOOGLE_API_KEY in .env",
    +        ),
    +        (
    +            "MINIMAX_API_KEY",
    +            "bad-minimax-key",
    +            "minimax.io",
    +            {},
    +            {"logged_in": True, "region": "global"},
    +            "Check MINIMAX_API_KEY in .env",
    +        ),
    +    ],
    +)
    +def test_run_doctor_ignores_invalid_direct_keys_when_oauth_fallback_is_healthy(
    +    monkeypatch,
    +    tmp_path,
    +    env_key,
    +    bad_key,
    +    failing_host,
    +    gemini_oauth_status,
    +    minimax_oauth_status,
    +    unexpected_issue,
    +):
    +    out = _run_doctor_with_healthy_oauth_fallback(
    +        monkeypatch,
    +        tmp_path,
    +        env_key=env_key,
    +        bad_key=bad_key,
    +        failing_host=failing_host,
    +        gemini_oauth_status=gemini_oauth_status,
    +        minimax_oauth_status=minimax_oauth_status,
    +    )
    +
    +    assert "invalid API key" in out
    +    assert unexpected_issue not in out
    ```
- **#26704: fix(doctor): suppress stale direct-key issues when oauth is healthy**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `d0a183cad`, `c4bda3f27`, `#26853`

## Fix status

- Merged to main in PR #26853 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-16`.
