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
- 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
- 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.
- Open a different chat, then click back into the first one to resume it.
- 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.
Summary
In the desktop (and TUI gateway) app, clicking any older chat to resume it fails with:
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_providersAnthropic-compatible endpoint (api_mode: anthropic_messages), with the key in.env.Environment
3e74f75eapps/desktop)custom:<name>endpoint configured undercustom_providersRoot cause
tui_gateway/server.py::_stored_session_runtime_overrides()(currentmain, around line 1441) restores the model/provider a chat actually used, reading provider from:For sessions that never had an explicit
/modelswitch,model_configwas never persisted (it's written only by_persist_live_session_runtime, which fires on/model, reasoning changes, etc.). Normal turns persist onlybilling_providerviaconversation_loop.py(billing_provider=agent.provider). For acustom:<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:Provider resolution returns no client → the
RuntimeErrorat ~line 866 is raised →session.resumereturnsresume 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 barebilling_provider.Reproduction
/model). The session row now hasbilling_provider="custom"andmodel_config=NULL.resume failed: No LLM provider configured.The stored row looks like:
and
_stored_session_runtime_overridesreturnsprovider_override="custom",model_override.provider="custom"— neither routable.Suggested fix
A bare billing class (
custom/auto/openrouter) with nobase_urlis 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:custom:<name>providers that carry abase_url(restored verbatim), andanthropic,openai-codex, ...) which are routable without abase_url.I have a patch + regression tests for
_stored_session_runtime_overridesready and am happy to open a PR if useful.Workarounds (for anyone hitting this now)
hermes chat --resume <session_id>works.model_configwith the full provider info ({"model": ..., "provider": "custom:<name>", "base_url": ..., "api_mode": ...}); desktop resume re-reads the DB so no restart is needed.