From 7338e5d9ba94c1d90a644d0588ac003d1aaee350 Mon Sep 17 00:00:00 2001 From: kshitij <82637225+kshitijk4poor@users.noreply.github.com> Date: Fri, 8 May 2026 01:58:54 -0700 Subject: [PATCH] fix(model-switch): prevent stale Ollama credentials after provider switch (#21703) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When switching from a custom local provider (e.g. ollama-launch) to a cloud provider, two bugs caused the CLI to misbehave: 1. _explicit_api_key/_explicit_base_url were only updated when the switch result had non-empty values (guarded by `if result.api_key:` etc.). If the previous provider set these to Ollama values ("ollama", "http://127.0.0.1:11434/v1"), those stale values leaked into the next turn's _ensure_runtime_credentials() call and were forwarded to the new provider's API endpoint, causing authentication/routing failures. Fix: unconditionally write result.api_key/base_url into the explicit fields after every successful switch. An empty string is the correct sentinel — it tells _ensure_runtime_credentials to re-resolve from the auth store / config rather than forwarding a stale override. 2. In AIAgent.switch_model(), `self.base_url = base_url or self.base_url` kept the old Ollama localhost URL whenever the incoming base_url was an empty string. For providers that use a native SDK (not an OpenAI-compat endpoint), the caller passes base_url="" and expects the agent to clear the field — not silently inherit Ollama's address. Fix: only update self.base_url when base_url is truthy. 3. _handle_model_picker_selection() was called from the prompt_toolkit Enter key binding without any exception guard. Any unexpected error in the model-selection code path propagated through prompt_toolkit's key-binding dispatcher and caused the entire TUI to exit — which the user sees as "the terminal exits when I switch providers". Fix: wrap the call in try/except and close the picker on failure. --- cli.py | 20 +++++++++++++++----- run_agent.py | 8 +++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cli.py b/cli.py index 08a9bb94ce..531716885b 100644 --- a/cli.py +++ b/cli.py @@ -5804,12 +5804,15 @@ class HermesCLI: self.model = result.new_model self.provider = result.target_provider self.requested_provider = result.target_provider + # Always overwrite explicit overrides so stale credentials from the + # previous provider (e.g. Ollama api_key/base_url) don't leak into + # the new provider's credential resolution on the next turn. + self._explicit_api_key = result.api_key + self._explicit_base_url = result.base_url if result.api_key: self.api_key = result.api_key - self._explicit_api_key = result.api_key if result.base_url: self.base_url = result.base_url - self._explicit_base_url = result.base_url if result.api_mode: self.api_mode = result.api_mode @@ -6027,12 +6030,15 @@ class HermesCLI: self.model = result.new_model self.provider = result.target_provider self.requested_provider = result.target_provider + # Always overwrite explicit overrides so stale credentials from the + # previous provider (e.g. Ollama api_key/base_url) don't leak into + # the new provider's credential resolution on the next turn. + self._explicit_api_key = result.api_key + self._explicit_base_url = result.base_url if result.api_key: self.api_key = result.api_key - self._explicit_api_key = result.api_key if result.base_url: self.base_url = result.base_url - self._explicit_base_url = result.base_url if result.api_mode: self.api_mode = result.api_mode @@ -10443,7 +10449,11 @@ class HermesCLI: # --- /model picker modal --- if self._model_picker_state: - self._handle_model_picker_selection() + try: + self._handle_model_picker_selection() + except Exception as _exc: + _cprint(f" ✗ Model selection failed: {_exc}") + self._close_model_picker() event.app.current_buffer.reset() event.app.invalidate() return diff --git a/run_agent.py b/run_agent.py index 403dba4e78..2646301b3e 100644 --- a/run_agent.py +++ b/run_agent.py @@ -2386,7 +2386,13 @@ class AIAgent: # ── Swap core runtime fields ── self.model = new_model self.provider = new_provider - self.base_url = base_url or self.base_url + # Use new base_url when provided; only fall back to current when the + # new provider genuinely has no endpoint (e.g. native SDK providers). + # Without this guard the old provider's URL (e.g. Ollama's localhost + # address) would persist silently after switching to a cloud provider + # that returns an empty base_url string. + if base_url: + self.base_url = base_url self.api_mode = api_mode # Invalidate transport cache — new api_mode may need a different transport if hasattr(self, "_transport_cache"):