Skip to content

fix(cli,auth): resolve base_url_env_var from ~/.hermes/.env - #20656

Open
haosenwang1018 wants to merge 1 commit into
NousResearch:mainfrom
haosenwang1018:fix/18757-base-url-dotenv-resolution
Open

fix(cli,auth): resolve base_url_env_var from ~/.hermes/.env#20656
haosenwang1018 wants to merge 1 commit into
NousResearch:mainfrom
haosenwang1018:fix/18757-base-url-dotenv-resolution

Conversation

@haosenwang1018

Copy link
Copy Markdown

Issue

Closes #18757

Root cause

Provider base URLs declared via base_url_env_var (e.g. XIAOMI_BASE_URL, ARCEE_BASE_URL) resolved only through os.getenv — they didn't see ~/.hermes/.env. API keys already used get_env_value (which reads .env), so the function had two halves with inconsistent semantics: API key found, base URL silently fell back to the registry default.

The original report describes Xiaomi's token-plan-cn-cn.xiaomimimo.com endpoint silently regressing to the public api.xiaomimimo.com default → 401s on auxiliary tasks. The same inconsistency affected every API-key provider with a base_url_env_var (Arcee, GLM, Kimi, GMI, MiniMax, Stepfun, GMI, AI Gateway, OpenCode, Kilocode, HF, TokenHub, Ollama, Bedrock, Azure Foundry, etc.).

Fix

Four call sites needed the same .env-aware lookup. The bug report named the first one; the rest were grep'd from the same access pattern:

File Function Used by
hermes_cli/auth.py resolve_api_key_provider_credentials runtime auxiliary client (the original bug)
hermes_cli/auth.py get_api_key_provider_status hermes doctor status output
hermes_cli/auth.py resolve_external_process_provider_credentials Copilot ACP runtime
hermes_cli/runtime_provider.py _build_runtime_for_api_key_provider (around the existing pconfig.base_url_env_var block) main agent runtime selection

All four now use the same fallback pattern that API-key resolution already uses elsewhere in the file:

get_env_value(pconfig.base_url_env_var) or os.getenv(pconfig.base_url_env_var, "")

Behavior when the value is set in the shell environment is unchanged.

Tests

tests/hermes_cli/test_provider_base_url_dotenv.py (new) covers:

  • A .env-only base URL is now picked up by resolve_api_key_provider_credentials
  • The same value is reflected by get_api_key_provider_status (so hermes doctor no longer lies when a .env base URL is set)
  • The shell-env path still works (regression guard for the other direction)

Existing test_arcee_provider.py and test_runtime_provider_resolution.py also pass unchanged (139 tests in this scope, all green).

Closes NousResearch#18757

Provider base URLs declared via ``base_url_env_var`` were resolved
through ``os.getenv`` only — bypassing ``~/.hermes/.env``. API keys
already used ``get_env_value`` (which reads ``.env``), so users with
a base URL set only in ``.env`` (not exported in the shell) ended up
with a working API key but the registry default base URL.

The original report describes Xiaomi's ``token-plan-cn`` endpoint
silently regressing to the public ``api.xiaomimimo.com`` default,
producing 401s on auxiliary tasks. The same inconsistency affects
every API-key provider with a ``base_url_env_var``.

Four call sites were inconsistent and now use the
``get_env_value(...) or os.getenv(...)`` fallback (matching the
existing API-key resolution pattern):

- ``auth.get_api_key_provider_status`` (status / ``hermes doctor``)
- ``auth.resolve_api_key_provider_credentials`` (the original report)
- ``auth.resolve_external_process_provider_credentials`` (Copilot ACP)
- ``runtime_provider._build_runtime_for_api_key_provider``
  (auxiliary client base URL plumbing)

Tests cover the regression (``.env``-only base URL is now picked up)
and pin the existing shell-env behavior so we don't regress in the
other direction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools labels May 6, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #18948 — both fix base_url_env_var resolution to use get_env_value() instead of bare os.getenv(). Please coordinate with that PR or close one.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing the API-key and external-process resolver paths. The underlying dotenv-only base-URL defect is still present on current main: hermes_cli/auth.py:6204, :6236, :6391, and :6449 use os.getenv() directly.

Problems

  • The runtime_provider.py hunk is stale. Current hermes_cli/runtime_provider.py:1464 uses _getenv(), which routes through the profile secret scope at hermes_cli/runtime_provider.py:43 / agent/secret_scope.py:123; a direct get_env_value() replacement would bypass that newer isolation mechanism.
  • The PR does not cover current sibling reads in hermes_cli/auth.py:6236 or hermes_cli/model_switch.py:1560.
  • tests/hermes_cli/test_provider_base_url_dotenv.py mocks get_env_value; use a temporary HERMES_HOME/.env instead so the regression verifies real dotenv loading and profile scoping.

Suggested changes

  • Rebase the auth-path fix onto the current functions, preserve secret-scope semantics in runtime resolution, and add the missing status/model-switch paths if they remain intended behavior.
  • Add an end-to-end temporary-home regression test rather than mocking the dotenv accessor.

Automated hermes-sweeper review.

if pconfig.base_url_env_var:
env_url = os.getenv(pconfig.base_url_env_var, "").strip().rstrip("/")
from hermes_cli.config import get_env_value
env_url = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main routes this read through _getenv() so multiplexed gateway turns use agent.secret_scope.get_secret() rather than raw process/environment access. Please rework this hunk against the current resolver instead of importing get_env_value() directly.

"""``resolve_api_key_provider_credentials()`` must consult ``.env`` for
the base URL — not only the shell environment."""

def test_dotenv_base_url_used_when_shell_env_unset(self, monkeypatch):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mocks the accessor being validated, so it cannot verify real HERMES_HOME/.env loading or profile-path behavior. Please use a temporary Hermes home with an actual .env file and clear the shell variable.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: resolve_api_key_provider_credentials() uses os.getenv for base_url_env_var — misses ~/.hermes/.env values

3 participants