Skip to content

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

@AIalliAI

Description

@AIalliAI

Summary

tui_gateway/server.py::_runtime_model_config() persists the live agent's resolved provider name into the session row's model_config JSON. For any named providers: / custom_providers: entry (e.g. one called mimo-v2.5-pro), agent.provider is the literal string "custom" — the entry name is lost, and the api_key is (deliberately) never persisted.

On session.resume or _reset_session_agent (/new), _stored_session_runtime_overrides() feeds that stored provider="custom" back into _make_agent()resolve_runtime_provider(requested="custom", target_model=...). _get_named_custom_provider("custom") cannot match an entry named mimo-v2.5-pro, so depending on config the rebuild either:

  • raises No LLM provider configured. Run hermes model... (resume fails outright), or
  • silently resolves placeholder credentials ("no-key-required") against the patched-back base_url — every subsequent turn then 401s against the user's real endpoint.

Distinct from #44022 (sibling bug)

#44022 covers sessions where model_config was never persisted and only the bare billing_provider="custom" row column exists. This issue is the complementary path: model_config is persisted (any /model switch, reasoning change, etc. triggers _persist_live_session_runtime), but with the lossy resolved "custom" instead of the entry identity. Fixing #44022's restore-path heuristic does not help here, because these rows do carry a base_url — the override is restored verbatim, with unusable credentials.

Same resolved-vs-requested identity-loss family as the investigation in #44099.

Reproduction (dynamic)

With a config like:

custom_providers:
  - name: mimo-v2.5-pro
    base_url: https://token-plan-cn.xiaomimimo.com/v1
    api_key: sk-...
  1. resolve_runtime_provider(requested="mimo-v2.5-pro", ...) → resolves fine, provider="custom", entry api_key present. Agent works.
  2. Round-trip the resulting agent through _runtime_model_config() → stored model_config has provider: "custom", base_url: ..., no api_key.
  3. _stored_session_runtime_overrides() on that row → model_override.provider == "custom".
  4. _make_agent()resolve_runtime_provider(requested="custom") → api_key resolves empty → AIAgent.__init__ raises RuntimeError: No LLM provider configured.

Suggested fix

Persist the requested/entry identity, not the resolved one: when agent.provider == "custom" and the base_url matches a providers:/custom_providers: entry, store the canonical custom:<normalized-name> menu key (the same form a live /model switch already records via pdef.id, and which _get_named_custom_provider resolves). For rows persisted before the fix, _make_agent can perform the same base_url → entry recovery, falling back to passing the stored base_url as explicit_base_url so the direct-alias branch still targets the session's endpoint.

PR with fix + regression tests incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/configConfig system, migrations, profilescomp/gatewayGateway runner, session dispatch, deliverycomp/tuiTerminal UI (ui-tui/ + tui_gateway/)type/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions