NousResearch/hermes-agent · error · ValueError

switch_model: no base_url resolved for provider '{new_provid

Error message

switch_model: no base_url resolved for provider '{new_provider}' (switching from '{old_provider}'); refusing to keep the previous provider's endpoint

What it means

switch_model refuses to change providers when the new provider's base_url resolves empty. Keeping the previous provider's endpoint would silently send provider B's traffic to provider A's URL, and (per the comment citing #47828) the mis-applied state would persist on every subsequent turn until restart. Re-selecting the SAME provider with an empty base_url (credential-only refresh) is still allowed.

Source

Thrown at agent/agent_runtime_helpers.py:2583

        # provider is actually changing, do NOT fall back to the current
        # (old provider's) URL — that silently pairs the new provider label
        # with the previous provider's endpoint (e.g. new_provider=minimax
        # paired with the leftover api.githubcopilot.com URL), and every
        # request after the switch 400s at the wrong host. This mismatched
        # pair also gets snapshotted into _primary_runtime below, so it
        # keeps re-applying on every subsequent turn until a full restart.
        # Fail loud instead: the caller (model_switch.switch_model())
        # already resolves base_url for every real provider, so an empty
        # value here means resolution failed upstream, not that the
        # provider genuinely has none. Re-selecting the SAME provider with
        # an empty base_url (e.g. a credential-only refresh) is still fine
        # to keep the current URL. See #47828.
        old_norm_provider = (old_provider or "").strip().lower()
        new_norm_provider = (new_provider or "").strip().lower()
        if base_url:
            agent.base_url = base_url
        elif old_norm_provider != new_norm_provider:
            raise ValueError(
                f"switch_model: no base_url resolved for provider "
                f"'{new_provider}' (switching from '{old_provider}'); "
                "refusing to keep the previous provider's endpoint"
            )
        agent.api_mode = api_mode
        # Invalidate transport cache — new api_mode may need a different transport
        if hasattr(agent, "_transport_cache"):
            agent._transport_cache.clear()
        if api_key:
            agent.api_key = api_key

        # ── Reload credential pool for the new provider (issue #52727) ──
        # Without this, ``recover_with_credential_pool`` sees a
        # ``pool.provider != agent.provider`` mismatch and short-circuits,
        # leaving the new provider with no rotation/recovery on 401/429 and
        # burning the original pool's entries. Only reload when the provider
        # actually changed (or the pool was missing) — re-selecting the same
        # provider must not churn the pool reference. A reload failure is

View on GitHub (pinned to c896c09c42)

Solutions

  1. Ensure the target provider's profile (plugins/model-providers/<name>) defines a base_url, or supply model.base_url in config.yaml
  2. Check the provider name for typos in the `hermes model` selection
  3. If the switch was meant as a credential-only refresh, keep the provider identical instead of naming a different one
  4. Update the provider plugin if its ProviderProfile shape changed
Defensive patterns

Strategy: validation

Validate before calling

def can_switch(old_provider: str, new_provider: str, new_base_url) -> bool:
    if new_base_url:
        return True
    return (old_provider or "").strip().lower() == (new_provider or "").strip().lower()

assert can_switch(old_provider, new_provider, resolved_base_url), (
    f"refusing switch to {new_provider!r}: no base_url resolved"
)

Try / catch

try:
    switch_model(new_provider=new_provider, base_url=resolved_url)
except ValueError as e:
    if "no base_url resolved" in str(e):
        abort_switch_and_keep_current()  # current endpoint stays valid
    else:
        raise

Prevention

When it happens

Trigger: model_switch.switch_model() reaches this helper with new_provider differing from old_provider (case-insensitive) while base_url is empty/None — provider-profile resolution produced no default endpoint and none was passed in.

Common situations: A custom provider plugin registered without a default base_url; provider name typo after a plugin rename; config resolving the provider credential-only with no endpoint; provider plugin not yet discovered at switch time.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/c2772f36066bb4e3. Report an issue: GitHub.