Skip to content

fix(doctor): use x-goog-api-key for Gemini connectivity probe (#26623)#26922

Closed
therahul-yo wants to merge 1 commit into
NousResearch:mainfrom
therahul-yo:fix/26623-doctor-gemini-x-goog-api-key
Closed

fix(doctor): use x-goog-api-key for Gemini connectivity probe (#26623)#26922
therahul-yo wants to merge 1 commit into
NousResearch:mainfrom
therahul-yo:fix/26623-doctor-gemini-x-goog-api-key

Conversation

@therahul-yo

Copy link
Copy Markdown
Contributor

Summary

Fixes #26623. hermes doctor reports a valid GOOGLE_API_KEY as ✗ gemini (invalid API key) because the generic API-key connectivity probe sends 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 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_provider in hermes_cli/doctor.py builds one set of headers for every provider:

headers = {
    "Authorization": f"Bearer {key}",
    "User-Agent": _HERMES_USER_AGENT,
}

Gemini (added to the probe list dynamically from plugins/model-providers/gemini) needs x-goog-api-key instead. Bearer auth → 401 → false negative.

Approach

Extract the header decision into a small _build_apikey_probe_headers(pname, base, default_url, key) helper that:

  • Returns {"x-goog-api-key": key, ...} when the request targets a Gemini-shaped endpoint
  • Returns {"Authorization": f"Bearer {key}", ...} for everyone else

Gemini is recognised by three independent signals (any one suffices):

  1. base_url_host_matches(base, "generativelanguage.googleapis.com") — user-overridden base URL
  2. base_url_host_matches(default_url, "generativelanguage.googleapis.com") — static default for the canonical Gemini profile
  3. pname.lower() in {"gemini", "google", "google / gemini"} — fallback when the URL signals are missing

The substring-attack defence already lives inside base_url_host_matches (utils.py), so https://evil.com/generativelanguage.googleapis.com/v1 does not trip the Gemini branch.

What this does NOT change

  • Non-Gemini providers (OpenRouter, DeepSeek, Kimi, MiniMax, Z.AI/GLM, Hugging Face, etc.) keep Authorization: Bearer — behaviour-preserving.
  • Bedrock, Anthropic-native, OAuth providers — unchanged (they have their own probe paths).
  • The Kimi User-Agent override layer still runs after the helper, just like before.

Test plan

14 new tests in tests/hermes_cli/test_doctor_apikey_probe_headers.py:

Test Asserts
test_gemini_detected_by_default_url Static default URL routes to x-goog-api-key
test_gemini_detected_by_overridden_base_url GEMINI_BASE_URL override routes to x-goog-api-key
test_gemini_detected_by_name[6 forms] Name-based detection: gemini, Gemini, GEMINI, google, Google, Google / Gemini
test_openrouter/deepseek/kimi_uses_bearer Non-Gemini providers unaffected
test_substring_match_does_not_false_positive Hostile host containing generativelanguage.googleapis.com in the path does NOT route to x-goog-api-key
test_empty_pname_and_no_urls_defaults_to_bearer Defensive default
test_user_agent_always_set Both branches keep User-Agent
$ python -m pytest tests/hermes_cli/test_doctor_apikey_probe_headers.py -v
======================== 14 passed in 5.00s ========================

$ python -m pytest tests/hermes_cli/test_doctor.py tests/hermes_cli/test_doctor_command_install.py tests/hermes_cli/test_doctor_dedicated_provider_skip.py -q
=========================== 52 passed in 6.78s ===========================

Tests 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

…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>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard provider/gemini Google Gemini (AI Studio, Cloud Code) duplicate This issue or pull request already exists labels May 16, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #20642 which already implements this exact fix (Gemini doctor probe using x-goog-api-key). This is the 8th+ PR with this same change. See also: #22468, #22489, #24266, #25883, #26067.

@therahul-yo

Copy link
Copy Markdown
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 gh search prs --repo NousResearch/hermes-agent "x-goog-api-key" — that's now in my "before opening a PR" loop, alongside the timeline/body-ref method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have provider/gemini Google Gemini (AI Studio, Cloud Code) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: bug(cli): hermes doctor falsely reports Google Gemini API key as invalid (HTTP 401 due to OpenAI auth format)

2 participants