NousResearch/hermes-agent
Doctor suppressing stale direct-key issues with OAuth
Cluster 26693. Data as of
Nothing open on GitHub. Fix merged in #26853 on 16 May 2026. Check it covers your case.
The short answer
0open issues
0open PRs
3items grouped
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #26853. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #26853. That is separate from the fix event. - Still open
- 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.
Open issues0
Open PRs0
Fixes on record
-
#26853 fix(doctor): suppress stale direct-key issues when oauth is healthy (salvage of #26704)
files:
hermes_cli/doctor.py,scripts/release.py,tests/hermes_cli/test_doctor.pyShow the diff
-
hermes_cli/doctor.py@@ -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 "")) @@ -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@@ -92,6 +92,7 @@ "[email protected]": "1000Delta", "[redacted:email]": "szymonclawd", "[email protected]": "szymonclawd", + "[email protected]": "worlldz", "[redacted:email]": "luoyuctl", "[email protected]": "luoyuctl", "[email protected]": "teknium1", -
tests/hermes_cli/test_doctor.py@@ -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
-
1 PR closed without merging
From the close comments: done elsewhere 1
-
#26704 fix(doctor): suppress stale direct-key issues when oauth is healthy · done elsewhere →
d0a183c,c4bda3f,#26853
Closed issues · 1
- Closed#26693[Bug]: hermes doctor promotes invalid direct API keys into the final summary even when OAuth fallback is already healthy
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Merged PRs
- #26853
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 16 May 2026