# NousResearch/hermes-agent · Provider config, auth, and API error fixes

## Open now

- 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.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #54441. Verify it addresses your case.
- **Recommended:** read PR #54441 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-54441`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #3846: \[Bug\]: telegram bot responds with random error i dont know how to fix!

## Prior & competing PRs

- **#54441: fix(provider): auto+base_url bypasses cloud API when custom endpoint configured (#3846)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/runtime_provider.py`, `tests/hermes_cli/test_runtime_provider_resolution.py`
  - fix hunks:
  - `hermes_cli/runtime_provider.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```
- **#3997: fix(provider): auto+base_url bypasses cloud API when custom endpoint configured**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `b0b7ff0d7`, `#54441`, `#3846`

## Fix status

- Merged to main in PR #54441 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-28`.
- Subsystem: `gateway`
