Provider config, auth, and API error fixes
Cluster 3846 · gateway. Data as of
Nothing open on GitHub. Fix merged in #54441 on 28 Jun 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #54441. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #54441. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and PRs cover several adjacent but distinct themes: provider routing bugs (auto+base_url, OpenCode Go/Zen showing wrong models), authentication and credential problems (Nous 401 refresh, Telegram auth loss, OAuth gate 401s), and general API errors. No single unified theme, but most relate to provider configuration or auth failures.
Open issues0
Open PRs0
Fixes on record
-
#54441 fix(provider): auto+base_url bypasses cloud API when custom endpoint configured (#3846)
files:
hermes_cli/runtime_provider.py,tests/hermes_cli/test_runtime_provider_resolution.pyShow the diff
-
hermes_cli/runtime_provider.py@@ -1454,6 +1454,43 @@ def resolve_runtime_provider( custom_runtime["requested_provider"] = requested_provider return custom_runtime + # If provider is "auto" (or unset) but config.yaml has an explicit base_url + # pointing at a custom/local endpoint (e.g. Ollama at localhost:11434), + # route through the OpenAI-compatible resolver instead of letting + # resolve_provider() pick up an ANTHROPIC_API_KEY or OPENAI_API_KEY from + # the environment and send the request to a cloud API. Fixes #3846. + if not explicit_base_url and not explicit_api_key: + model_cfg = _get_model_config() + cfg_provider = str(model_cfg.get("provider") or "").strip().lower() + cfg_base_url = str(model_cfg.get("base_url") or "").strip() + if cfg_base_url and cfg_provider in ("auto", ""): + # Check that base_url isn't one of the well-known cloud API roots + # (OpenRouter, Anthropic, OpenAI). If it's something else (Ollama, + # LM Studio, vLLM, …) we honour it directly. The full detection + # logic lives in _resolve_openrouter_runtime; we just skip the + # resolve_provider() call so env-var credentials don't shadow it. + # Match on HOST, not substring, so a look-alike base_url + # (e.g. http://api.anthropic.com.attacker.test/v1, or one whose + # path merely contains "openai.com") cannot evade the bypass and + # leak a cloud credential. Mirrors the host-gating used for + # API-key selection in _resolve_openrouter_runtime. + _known_cloud_hosts = ( + "openrouter.ai", + "anthropic.com", + "openai.com", + ) + if not any( + base_url_host_matches(cfg_base_url, host) + for host in _known_cloud_hosts + ): + runtime = _resolve_openrouter_runtime( + requested_provider=requested_provider, + explicit_api_key=[redacted:secret-assignment] + explicit_base_url=explicit_base_url, + ) + runtime["requested_provider"] = requested_provider + return runtime + provider = resolve_provider( requested_provider, explicit_api_key=[redacted:secret-assignment] -
tests/hermes_cli/test_runtime_provider_resolution.py@@ -2893,3 +2893,102 @@ def test_resolve_runtime_provider_bedrock_nonclaude_target_model_uses_converse(m assert resolved["provider"] == "bedrock" assert resolved["api_mode"] == "bedrock_converse" assert resolved.get("bedrock_anthropic") is not True + + +def test_auto_provider_with_local_base_url_bypasses_anthropic_key(monkeypatch): + """provider:auto + base_url:localhost should NOT route to Anthropic even if + ANTHROPIC_API_KEY is set in the environment. Regression test for #3846. + + Users running Ollama locally had requests silently sent to Anthropic's API + because resolve_provider("auto") picked up ANTHROPIC_API_KEY before the + config.yaml base_url was checked. + """ + # ANTHROPIC_API_KEY is present in environment — should be ignored + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-fake-key") + monkeypatch.delenv("OPENAI_BASE_URL", raising=False) + monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + + monkeypatch.setattr( + rp, + "_get_model_config", + lambda: { + "default": "ollama/minimax-m2.7:cloud", + "provider": "auto", + "base_url": "http://localhost:11434", + }, + ) + + resolved = rp.resolve_runtime_provider() + + # Must NOT go to Anthropic's API + assert "anthropic.com" not in resolved.get("base_url", ""), ( + f"Expected custom endpoint, got Anthropic: {resolved}" + ) + assert resolved["base_url"] == "http://localhost:11434" + # provider should be custom/openrouter (OpenAI-compat), not anthropic + assert resolved["provider"] != "anthropic", ( + f"Should have routed to custom endpoint, not anthropic: {resolved}" + ) + + +def test_auto_provider_with_known_cloud_base_url_still_uses_anthropic(monkeypatch): + """provider:auto + base_url pointing to Anthropic should still use Anthropic. + + The local-endpoint bypass only applies to non-cloud endpoints; when the + configured base_url IS a cloud API root, resolve_provider() must run + normally and pick up ANTHROPIC_API_KEY. + """ + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-fake-key") + monkeypatch.delenv("OPENAI_BASE_URL", raising=False) + monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + + monkeypatch.setattr( + rp, + "_get_model_config", + lambda: { + "provider": "auto", + "base_url": "https://api.anthropic.com", + }, + ) + + resolved = rp.resolve_runtime_provider() + + # Cloud base_url → bypass does NOT fire → resolve_provider picks anthropic. + assert resolved["provider"] == "anthropic", ( + f"Cloud base_url must not be diverted to the custom resolver: {resolved}" + ) + + +def test_auto_provider_lookalike_cloud_host_does_not_bypass_to_cloud(monkeypatch): + """A look-alike host (api.anthropic.com.attacker.test) must be treated as a + custom endpoint, NOT mistaken for the real Anthropic cloud. + + Guards the host-match (vs naive substring) check in the local-endpoint + bypass: substring matching on "api.anthropic.com" would wrongly classify + this attacker-controlled host as cloud and hand it the ANTHROPIC_API_KEY. + """ + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-fake-key") + monkeypatch.delenv("OPENAI_BASE_URL", raising=False) + monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + + lookalike = "http://api.anthropic.com.attacker.test/v1" + monkeypatch.setattr( + rp, + "_get_model_config", + lambda: {"provider": "auto", "base_url": lookalike}, + ) + + resolved = rp.resolve_runtime_provider() + + # Host doesn't actually match anthropic.com → bypass fires → custom route, + # and the real Anthropic credential is NOT sent there. + assert resolved["provider"] != "anthropic", ( + f"Look-alike host must not be classified as Anthropic cloud: {resolved}" + ) + assert resolved["base_url"] == lookalike
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#3997 fix(provider): auto+base_url bypasses cloud API when custom endpoint configured · merged via another change →
b0b7ff0,#54441,#3846
Closed issues · 1
- Closed#3846[Bug]: telegram bot responds with random error i dont know how to fix!
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
- Subsystem
- gateway
- Merged PRs
- #54441
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 28 Jun 2026