Summary
The v11 → v12 config migration in hermes_cli/config.py:1809-1865 rewrites the legacy custom_providers: list into a new providers: dict, then deletes the list. But the runtime resolver and the fallback-activation resolver both read from custom_providers: (the list), so after the migration runs, named custom endpoints silently stop resolving at runtime.
Repro
Starting config (pre-migration, _config_version unset):
model:
default: openai/gpt-5.4
provider: openrouter
custom_providers:
- name: openai-direct
base_url: https://api.openai.com/v1
api_mode: codex_responses
fallback_providers:
- provider: openai-direct
model: gpt-5-mini
Trigger migration (hermes setup, or any path that reaches migrate_config). The file ends up as:
model:
default: openai/gpt-5.4
provider: openrouter
providers:
openai-direct:
api: https://api.openai.com/v1
name: openai-direct
transport: codex_responses
fallback_providers:
- provider: openai-direct
model: gpt-5-mini
_config_version: 16
Now when fallback activates:
hermes_cli.auth.AuthError: Unknown provider 'openai-direct'.
Because:
hermes_cli/runtime_provider.py:259 _get_named_custom_provider reads config.get(\"custom_providers\") and requires it to be a list — the migration just deleted it.
agent/auxiliary_client.py:1428-1429 (fallback activation path, reached via resolve_provider_client) calls the same _get_named_custom_provider.
Nothing in the codebase reads config[\"providers\"] at runtime. Grep confirms:
$ grep -rn 'config.get(\"providers\"\|config\[\"providers\"\]' hermes_cli/ agent/ run_agent.py
hermes_cli/config.py:1814 # migration target (write-only)
hermes_cli/config.py:1862 # migration target (write-only)
(The providers key in auth.py and model_switch.py refers to the unrelated auth_store[\"providers\"] persistence, not config.yaml.)
Impact
Any user who had custom_providers: and then upgrades past v11 loses runtime resolution for those endpoints. Fallback chains referencing the custom provider name throw AuthError silently in logs; smart-routing / auxiliary calls fall through to defaults. hermes doctor reports the config as healthy.
Suggested fix
One of the following:
- Update runtime resolvers to read from
providers: dict in addition to (or instead of) custom_providers: list. _get_named_custom_provider and the fallback path in auxiliary_client.py:1420-1450 both need the change. Schema mapping:
providers[key].api → base_url
providers[key].transport → api_mode
providers[key].default_model → model
providers[key].api_key → api_key
- Make the migration lossless — keep writing
custom_providers: (list) alongside providers: (dict) until runtime code is updated, then cut over in a single release.
- Drop the migration for now if
providers: dict was aspirational and isn't being wired up this cycle.
Happy to submit option (1) as a PR if that's the preferred direction.
Environment
- Hermes
1cec910b (main, 2026-04-13)
- Python 3.11, macOS arm64
_config_version: 16
Summary
The v11 → v12 config migration in
hermes_cli/config.py:1809-1865rewrites the legacycustom_providers:list into a newproviders:dict, then deletes the list. But the runtime resolver and the fallback-activation resolver both read fromcustom_providers:(the list), so after the migration runs, named custom endpoints silently stop resolving at runtime.Repro
Starting config (pre-migration,
_config_versionunset):Trigger migration (
hermes setup, or any path that reachesmigrate_config). The file ends up as:Now when fallback activates:
Because:
hermes_cli/runtime_provider.py:259 _get_named_custom_providerreadsconfig.get(\"custom_providers\")and requires it to be a list — the migration just deleted it.agent/auxiliary_client.py:1428-1429(fallback activation path, reached viaresolve_provider_client) calls the same_get_named_custom_provider.Nothing in the codebase reads
config[\"providers\"]at runtime. Grep confirms:(The
providerskey inauth.pyandmodel_switch.pyrefers to the unrelatedauth_store[\"providers\"]persistence, not config.yaml.)Impact
Any user who had
custom_providers:and then upgrades past v11 loses runtime resolution for those endpoints. Fallback chains referencing the custom provider name throwAuthErrorsilently in logs; smart-routing / auxiliary calls fall through to defaults.hermes doctorreports the config as healthy.Suggested fix
One of the following:
providers:dict in addition to (or instead of)custom_providers:list._get_named_custom_providerand the fallback path inauxiliary_client.py:1420-1450both need the change. Schema mapping:providers[key].api→base_urlproviders[key].transport→api_modeproviders[key].default_model→modelproviders[key].api_key→api_keycustom_providers:(list) alongsideproviders:(dict) until runtime code is updated, then cut over in a single release.providers:dict was aspirational and isn't being wired up this cycle.Happy to submit option (1) as a PR if that's the preferred direction.
Environment
1cec910b(main, 2026-04-13)_config_version: 16