Skip to content

fix(desktop): persist custom provider entry identity in session runtime, not the resolved "custom"#44109

Closed
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/custom-provider-session-persistence
Closed

fix(desktop): persist custom provider entry identity in session runtime, not the resolved "custom"#44109
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/custom-provider-session-persistence

Conversation

@AIalliAI

Copy link
Copy Markdown
Contributor

Fixes #44108

Problem

_runtime_model_config() (tui_gateway/server.py) persists the live agent's resolved provider into the session row's model_config JSON. For any named providers: / custom_providers: entry, agent.provider is the literal string "custom" — the entry name is lost, and the api_key is deliberately never persisted (credentials must keep coming from config/env, see _stored_session_runtime_overrides).

On session.resume / _reset_session_agent, the stored provider="custom" feeds resolve_runtime_provider(requested="custom"), which _get_named_custom_provider cannot match back to the named entry. Depending on config, the rebuild either raises No LLM provider configured. Run hermes model... (resume fails) or silently resolves placeholder credentials ("no-key-required") against the patched-back base_url.

Sibling of #44022 (which covers rows where model_config was never written and only the bare billing_provider column exists — that path is not changed here). Same resolved-vs-requested identity-loss family as the #44070 investigation in #44099.

Fix

Persist the requested/entry identity, not the resolved one:

  • hermes_cli/runtime_provider.py: new reverse lookup find_custom_provider_identity(base_url) mapping an endpoint URL back to the canonical custom:<normalized-name> menu key (scans providers: dict and legacy custom_providers: list; trailing-slash/case tolerant). The slug form matches what a live /model switch already records via pdef.id and what _get_named_custom_provider resolves.
  • tui_gateway/server.py::_runtime_model_config: when agent.provider == "custom" and the base_url maps to an entry, persist the custom:<name> key instead of the lossy "custom". Built-in providers never trigger the lookup.
  • tui_gateway/server.py::_make_agent: heals rows persisted before this fix — when the stored override is provider="custom" with a base_url, recover the entry identity the same way; if no entry matches, pass the stored base_url as explicit_base_url so the direct-alias branch resolves pool/env credentials for the session's actual endpoint instead of raising auth_unavailable.

Tests

tests/tui_gateway/test_custom_provider_session_persistence.py (style mirrors the #44070 regression tests):

  • _runtime_model_config persists custom:<name> for both config shapes; keeps bare "custom" when nothing matches; never persists api_key; built-ins untouched.
  • Full persist → _stored_session_runtime_overrides_make_agent round-trip through the real resolve_runtime_provider restores the entry's credentials (the exact path that raised before).
  • Legacy poisoned row (provider="custom" + base_url) heals via base_url recovery; unmatched legacy row keeps its endpoint via the direct-alias branch.

tests/hermes_cli/test_custom_provider_identity.py: unit coverage for the reverse lookup, including that the returned slug round-trips through _get_named_custom_provider.

Red/green verified: 5/7 gateway tests fail on origin/main sources, all pass with the fix. tests/test_tui_gateway_server.py, tests/tui_gateway/, tests/hermes_cli/ pass (the 12 pre-existing failures there — WSL/service-manager/browser-hint, macOS environment — fail identically on pristine origin/main).

🤖 Generated with Claude Code

_runtime_model_config persisted the live agent's RESOLVED provider into
the session row's model_config JSON. For any named providers:/
custom_providers: entry, agent.provider is the literal string "custom",
so the entry name was lost (and the api_key is deliberately never
persisted). On session.resume or _reset_session_agent the stored
provider="custom" fed resolve_runtime_provider(requested="custom"),
which cannot match a named entry — the rebuild either raised "No LLM
provider configured" or silently resolved placeholder credentials
against the patched-back base_url.

Persist the REQUESTED/entry identity instead: a new reverse lookup
find_custom_provider_identity(base_url) maps the endpoint URL back to
the canonical custom:<name> menu key. _runtime_model_config stores that
key; _make_agent performs the same recovery for rows persisted before
the fix, falling back to passing the stored base_url as
explicit_base_url so the direct-alias branch still targets the
session's endpoint when no entry matches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AIalliAI

Copy link
Copy Markdown
Contributor Author

Requesting maintainer review — this is ready to land from my side. Standalone fork CI is pending first-run approval here; the rollup branch in #44061 carrying this session's batch is fully green on upstream CI (all test shards, typecheck, e2e).

@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #45578 with your commit cherry-picked onto current main and authorship preserved.

Thanks for the persist-side custom provider identity fix: Desktop/TUI session runtime now persists/recover named custom providers as custom:<name> instead of lossy bare custom.

#45578

@teknium1 teknium1 closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop/TUI: session persistence stores resolved provider "custom", losing named custom_providers entry identity — resume rebuilds with no credentials

3 participants