Skip to content

[Bug]: Desktop/TUI session.resume fails with "No LLM provider configured" when session only stored a bare billing_provider (e.g. custom) #44022

@joey803

Description

@joey803

Summary

In the desktop (and TUI gateway) app, clicking any older chat to resume it fails with:

resume failed: No LLM provider configured. Run hermes model to select a provider, or run hermes setup for first-time configuration.

This happens even though the configured default provider is valid and new chats work fine. hermes chat --resume <id> from the CLI works correctly for the exact same sessions — only the desktop/TUI resume path fails.

In my case every pre-existing chat became unopenable after updating the desktop client. The default provider is a custom_providers Anthropic-compatible endpoint (api_mode: anthropic_messages), with the key in .env.

Environment

  • Hermes Agent v0.16.0 (2026.6.5), upstream 3e74f75e
  • macOS (Apple Silicon), desktop Electron app (apps/desktop)
  • Provider: a custom:<name> endpoint configured under custom_providers

Root cause

tui_gateway/server.py::_stored_session_runtime_overrides() (current main, around line 1441) restores the model/provider a chat actually used, reading provider from:

provider = str(
    model_config.get("provider")
    or model_config.get("billing_provider")
    or row.get("billing_provider")
    or ""
).strip()

For sessions that never had an explicit /model switch, model_config was never persisted (it's written only by _persist_live_session_runtime, which fires on /model, reasoning changes, etc.). Normal turns persist only billing_provider via conversation_loop.py (billing_provider=agent.provider). For a custom:<name> endpoint the billing bucket stored there is the bare string "custom".

So on resume, the bare billing class "custom" is restored as the provider identity (provider_override / model_override.provider).

Then in agent/agent_init.py (~line 808), a bare "custom" / "auto" / "openrouter" is treated as not a routable explicit provider:

if _explicit and _explicit not in {"auto", "openrouter", "custom"}:
    ...
if not getattr(agent, "_fallback_activated", False):
    raise RuntimeError("No LLM provider configured. Run `hermes model` ...")

Provider resolution returns no client → the RuntimeError at ~line 866 is raised → session.resume returns resume failed: ....

The CLI is unaffected because it does not go through this session-scoped runtime-restore path; it uses the configured default provider directly. That asymmetry points squarely at the restore path, not at provider config.

This appears to have been introduced by 6de3963e3 fix(desktop): keep model runtime state per session (#43702), which started restoring per-session runtime identity but doesn't account for sessions that only persisted a bare billing_provider.

Reproduction

  1. Configure a custom provider as the default:
    model:
      default: <your-model>
      provider: custom:<name>
    custom_providers:
    - name: <name>
      base_url: https://your-endpoint.example
      key_env: YOUR_API_KEY
      api_mode: anthropic_messages
  2. In the desktop app, start a chat and send a couple of messages (do not use /model). The session row now has billing_provider="custom" and model_config=NULL.
  3. Open a different chat, then click back into the first one to resume it.
  4. Result: resume failed: No LLM provider configured.

The stored row looks like:

model            : <your-model>
billing_provider : custom
model_config     : NULL

and _stored_session_runtime_overrides returns provider_override="custom", model_override.provider="custom" — neither routable.

Suggested fix

A bare billing class (custom / auto / openrouter) with no base_url is not a routable provider id and should not be restored as the provider identity — defer to config/env provider resolution (the same path the CLI uses), while still preserving:

  • fully-qualified custom:<name> providers that carry a base_url (restored verbatim), and
  • real explicit providers (anthropic, openai-codex, ...) which are routable without a base_url.

I have a patch + regression tests for _stored_session_runtime_overrides ready and am happy to open a PR if useful.

Workarounds (for anyone hitting this now)

  • Resume from the CLI: hermes chat --resume <session_id> works.
  • Or backfill the affected rows' model_config with the full provider info ({"model": ..., "provider": "custom:<name>", "base_url": ..., "api_mode": ...}); desktop resume re-reads the DB so no restart is needed.

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