github →

New request

#3405: Bug: Profile provider/model not respected in WebUI — session routes to global default provider instead of profile's configured provider

We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.

Issue
bughelp wantedsprint-candidate

Summary

When a chat is started in the WebUI with a profile selected, the session's provider is not resolved from that profile. If the chosen model string is not @provider:-qualified and no explicit provider is sent, the backend falls back to the global active provider from the model catalog. The result: a profile bound to one provider/key silently runs on a different provider.

This is the provider/model analogue of #3294 ("Profile Tool Configuration Not Respected in Web UI"). Same class of bug — profile selection is not authoritative; unset/implicit fields inherit the global default instead of the profile config.

Impact (why this is serious)

  • Wrong credentials/billing: a profile wired to an Anthropic API key runs against GitHub Copilot instead.
  • Wrong context window → silent truncation: the global default model entry can carry a much larger advertised context_length than the provider actually serves. The agent never triggers its own compression, the provider silently drops the oldest messages, and long single-subject chats "forget" earlier content with no warning. (This is how I noticed — the model pill happened to display Opus, but logs showed Copilot.)
  • Invisible: the composer shows the profile name and a model name but never the provider, so there is no UI signal that routing diverged from the profile.

Evidence

WebUI session whose selected profile (personal) is configured with provider: anthropic and its own ANTHROPIC_API_KEY. Actual agent run (from ~/.hermes/logs/agent.log):

conversation turn: session=<id> model=claude-opus-4.8 provider=copilot platform=api_server
OpenAI client created ... provider=copilot base_url=https://api.githubcopilot.com model=claude-opus-4.8
API call #1: model=claude-opus-4.8 provider=copilot in=62058 ...

Profile config (~/.hermes/profiles/personal/config.yaml): model.provider: anthropic. Expected provider=anthropic; observed provider=copilot.

Root cause (located)

api/routes.py, _resolve_session_model_and_provider (approx lines 1802–1842).

When model is present but not @provider:-qualified and requested_provider is empty, the function resolves against the catalog's active_provider (the global active provider) rather than the selected profile's configured provider. The profile's provider binding never enters this path. So a bare model id like claude-opus-4.8 inherits whatever the global default provider is (Copilot), regardless of the profile the user picked.

Steps to reproduce

  1. Create profile personal with model.provider: anthropic, model.default: claude-opus-4.8, Anthropic key in its .env.
  2. Set the global default provider to something else (e.g. copilot) with an Opus model entry.
  3. In the WebUI composer, select the personal profile and pick "Claude Opus 4.8" (bare, no @provider: qualifier).
  4. Send a message.
  5. Inspect ~/.hermes/logs/agent.log: the run shows provider=copilot, not anthropic.

Expected

When a profile is selected and no explicit per-chat provider override is given, the session must resolve the provider from the profile's config. The profile's provider should win over the global catalog active_provider.

Suggested fix direction

In the start-chat path, thread the selected profile's configured provider into provider resolution and have it take precedence over the global active_provider fallback when the model is not @provider:-qualified and no explicit override is supplied. (Mirrors the fix shape requested in #3294 for tools/skills.)

Environment

  • Hermes WebUI on master (around the post-#3251 release merge).
  • Provider in question: GitHub Copilot (advertises a 1,000,000-token context that the endpoint does not actually serve), vs intended Anthropic.

Related: #3294 (same "profile not respected" class), #3368 / #3360 (model-picker substring matching — adjacent, not the same).

Assessmentadvisory
bug●● medium85% confidence

Profile provider resolution falls back to global defaults instead of the profile-bound configuration in session routes.

Likely files
  • api/routes.py
  • api/profiles.py
  • static/js/composer.js
  • tests/test_profiles.py
Create the request

This opens a fresh agent run and a draft PR for issue #3405.

Cancel