fix(doctor): use x-goog-api-key for Gemini connectivity probe (#26623)#26922
Closed
therahul-yo wants to merge 1 commit into
Closed
fix(doctor): use x-goog-api-key for Gemini connectivity probe (#26623)#26922therahul-yo wants to merge 1 commit into
therahul-yo wants to merge 1 commit into
Conversation
…search#26623) ``hermes doctor`` reported a perfectly valid ``GOOGLE_API_KEY`` as ``✗ gemini (invalid API key)`` because the generic API-key probe sent ``Authorization: Bearer <key>`` to every provider — including Google AI Studio, which only accepts ``x-goog-api-key`` and answers Bearer with HTTP 401. The doctor then mapped the 401 to "invalid API key", even though the main agent works fine with the exact same key (it uses a native Gemini client, not the generic OpenAI-compat probe). Extract the header decision into a small ``_build_apikey_probe_headers`` helper that picks ``x-goog-api-key`` for Gemini and keeps ``Authorization: Bearer`` for everyone else. Gemini is recognised by - ``base_url_host_matches`` against either the user-overridden base URL or the static default URL (covers self-hosted Gemini-compat proxies that keep the canonical host), - or the canonical provider name / its aliases (``gemini``, ``google``, ``Google / Gemini``). The substring guard inside ``base_url_host_matches`` already protects against attacker-controlled hosts that contain ``generativelanguage.googleapis.com`` in the path. Adds 14 regression tests in ``test_doctor_apikey_probe_headers.py`` covering the three detection paths, the substring false-positive defence, and that non-Gemini providers still get Bearer auth. The existing 52 doctor tests still pass — no behaviour change for any provider that already worked. Closes NousResearch#26623 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collaborator
Contributor
Author
|
Apologies — closing as confirmed duplicate. My duplicate check looked for PRs that cross-reference issue #26623, but PR #20642 (and #22286/#22468/#22489/#21490/#24033/#24266/#26067) was opened before #26623 existed and so never appeared in either the issue timeline or PR-body search. The check that would have caught this in one query is |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #26623.
hermes doctorreports a validGOOGLE_API_KEYas✗ gemini (invalid API key)because the generic API-key connectivity probe sendsAuthorization: Bearer <key>to every provider — including Google AI Studio, which only acceptsx-goog-api-keyand answers Bearer with HTTP 401. The doctor then maps 401 to "invalid API key", even though the main agent works fine with the same key (it uses the native Gemini client, not the generic OpenAI-compat probe).Root cause
_probe_apikey_providerinhermes_cli/doctor.pybuilds one set of headers for every provider:Gemini (added to the probe list dynamically from
plugins/model-providers/gemini) needsx-goog-api-keyinstead. Bearer auth → 401 → false negative.Approach
Extract the header decision into a small
_build_apikey_probe_headers(pname, base, default_url, key)helper that:{"x-goog-api-key": key, ...}when the request targets a Gemini-shaped endpoint{"Authorization": f"Bearer {key}", ...}for everyone elseGemini is recognised by three independent signals (any one suffices):
base_url_host_matches(base, "generativelanguage.googleapis.com")— user-overridden base URLbase_url_host_matches(default_url, "generativelanguage.googleapis.com")— static default for the canonical Gemini profilepname.lower() in {"gemini", "google", "google / gemini"}— fallback when the URL signals are missingThe substring-attack defence already lives inside
base_url_host_matches(utils.py), sohttps://evil.com/generativelanguage.googleapis.com/v1does not trip the Gemini branch.What this does NOT change
Authorization: Bearer— behaviour-preserving.Test plan
14 new tests in
tests/hermes_cli/test_doctor_apikey_probe_headers.py:test_gemini_detected_by_default_urlx-goog-api-keytest_gemini_detected_by_overridden_base_urlGEMINI_BASE_URLoverride routes tox-goog-api-keytest_gemini_detected_by_name[6 forms]gemini,Gemini,GEMINI,google,Google,Google / Geminitest_openrouter/deepseek/kimi_uses_bearertest_substring_match_does_not_false_positivegenerativelanguage.googleapis.comin the path does NOT route tox-goog-api-keytest_empty_pname_and_no_urls_defaults_to_bearertest_user_agent_always_setUser-AgentTests run under an isolated
HERMES_HOME=~/.hermes-pr3-test— no touch of the developer's live~/.hermes.Files
hermes_cli/doctor.py—+35 / -4(new helper + inline replacement + typing import)tests/hermes_cli/test_doctor_apikey_probe_headers.py—+128(new file, 14 tests)🤖 Generated with Claude Code