Skip to content

fix(model-switch): prevent stale Ollama credentials after provider switch#21703

Merged
alt-glitch merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/model-switch-stale-credentials-from-ollama
May 8, 2026
Merged

fix(model-switch): prevent stale Ollama credentials after provider switch#21703
alt-glitch merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/model-switch-stale-credentials-from-ollama

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

When switching FROM a named custom provider (e.g. ollama-launch) to any cloud provider, the CLI had three bugs that combined to produce:

  1. Chats stop working — API calls go to the wrong endpoint/key
  2. Terminal exits — any unexpected error in the picker's Enter handler propagates through prompt_toolkit and tears down the TUI

Bug 1: Stale _explicit_api_key / _explicit_base_url (cli.py)

_apply_model_switch_result and _handle_model_switch only wrote the new credentials when the switch result was non-empty:

if result.api_key:          # only if non-empty
    self._explicit_api_key = result.api_key
if result.base_url:         # only if non-empty
    self._explicit_base_url = result.base_url

Ollama users have model.api_key: ollama and model.base_url: http://127.0.0.1:11434/v1 in their config. These got set into _explicit_* on first use. When switching to a cloud provider whose result had non-empty values these got overwritten, but there are edge cases (e.g. native-SDK providers) where the result's base_url or api_key is empty — leaving the Ollama values in place for the next _ensure_runtime_credentials() call.

Fix: unconditionally assign self._explicit_api_key = result.api_key and self._explicit_base_url = result.base_url after every successful switch. An empty string is the correct sentinel — it tells resolve_runtime_provider to re-derive credentials from the auth store rather than forwarding a stale explicit override.

Bug 2: Old Ollama URL persists in agent (run_agent.py)

self.base_url = base_url or self.base_url   # "or" keeps old URL when base_url=""

A provider that uses a native SDK path passes base_url="" to agent.switch_model(). The or short-circuit silently kept http://127.0.0.1:11434/v1 — so the agent's OpenAI client was built targeting the local Ollama server.

Fix: only update self.base_url when base_url is truthy (i.e. if base_url: self.base_url = base_url).

Bug 3: Uncaught exception in picker Enter handler exits TUI

_handle_model_picker_selection() was called bare from the prompt_toolkit Enter key binding (no try/except). Any unhandled exception propagated through prompt_toolkit's dispatcher, was re-raised from app.run(), and escaped the narrow except (EOFError, KeyboardInterrupt, BrokenPipeError, KeyError, OSError) handler in run() — causing process exit.

Fix: wrap the call in try/except; show an error line and close the picker on failure.

Test plan

  • Start hermes with model.provider: ollama-launch in config
  • Type /model, navigate to a cloud provider (e.g. OpenRouter), select a model
  • Send a chat message — it should route to the new provider, not Ollama
  • Verify no terminal exit occurs if picker throws (e.g. temporarily break _handle_model_picker_selection)
  • Existing switch_model unit tests pass (python -m pytest tests/ -k switch_model -q)

…itch

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.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard provider/ollama Ollama / local models labels May 8, 2026

@alt-glitch alt-glitch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review — Verified ✅

Tested the fix E2E by replaying the credential state machine through the actual switch_model + resolve_runtime_provider pipeline with a real Ollama cloud config (https://ollama.com/v1).

Bug 1 (cli.py) — the real fix

Reproduced the stale credential leak:

Old code (main):
  Step 3: /model qwen3.5:397b --provider ollama-cloud
    → _explicit_base_url = 'https://ollama.com/v1'
  Step 4: /model switch to native provider (empty result.api_key/base_url)
    → _explicit_base_url = 'https://ollama.com/v1'  ← STALE, not cleared!
  Step 5: resolve_runtime_provider(requested='anthropic', explicit_base_url='https://ollama.com/v1')
    → Routes to Ollama instead of Anthropic 🐛

Fix (this PR):
  Step 4: _explicit_base_url = ''  ← always set, clearing stale value
  Step 5: resolve_runtime_provider(requested='anthropic', explicit_base_url='')
    → Re-derives from auth store → Anthropic ✓

Bug 2 (run_agent.py) — clarity refactor

base_url or self.base_urlif base_url: self.base_url = base_url are semantically identical (both preserve old value when falsy, overwrite when truthy). This is a readability improvement, not a behavioral change. Fine to include but worth noting it's defense-in-depth, not a fix.

Bug 3 (picker crash) — resilience

try/except around _handle_model_picker_selection() with graceful _close_model_picker() on failure. Reasonable defensive coding.

Tests

  • All 12 existing switch_model tests pass
  • Ollama cloud endpoint verified live (200 OK, qwen3.5:397b responds)
  • /model sonnet resolves correctly from custom provider context

@alt-glitch alt-glitch merged commit 7338e5d into NousResearch:main May 8, 2026
5 of 7 checks passed
RationallyPrime pushed a commit to RationallyPrime/hermes-agent that referenced this pull request May 8, 2026
…itch (NousResearch#21703)

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.
JZKK720 pushed a commit to JZKK720/hermes-agent that referenced this pull request May 11, 2026
…itch (NousResearch#21703)

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.
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
…itch (NousResearch#21703)

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.
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
…itch (NousResearch#21703)

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.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…itch (NousResearch#21703)

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.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…itch (NousResearch#21703)

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.
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…itch (NousResearch#21703)

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists provider/ollama Ollama / local models type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants