_model_flow_custom() saved model.provider and model.base_url to disk
via its own load_config/save_config cycle, but never updated the
setup wizard's in-memory config dict. The wizard's final
save_config(config) then overwrote the custom settings with the
stale default string model value.
Fix: after saving to disk, also mutate the caller's config dict so
the wizard's final save preserves model.provider='custom' and the
base_url. Both the model_name and no-model_name branches are
covered.
Added regression tests that simulate the full wizard flow including
the final save_config(config) call — the step that was previously
untested.
Summary
Fixes #4172. Supersedes #4174 (different root cause than described there).
The actual bug:
_model_flow_custom()savedmodel.provider: "custom"andmodel.base_urlto disk via its owncfg = load_config()/save_config(cfg)cycle — but never updated the setup wizard's in-memoryconfigdict. The wizard's finalsave_config(config)at setup.py line 1752 then overwrote the custom settings with the stale default (model: "anthropic/claude-opus-4.6"string).After this,
resolve_requested_provider()saw nomodel.providerin config, returned"auto", and the auto-detection chain picked up staleOPENROUTER_API_KEYfrom.env→ traffic routed to OpenRouter.Every other provider in the setup wizard uses
_set_model_provider(config, ...)which correctly updates the wizard's dict. The custom flow was the only one using a separateload_config().Fix: After saving to disk, also mutate the caller's
configdict so the wizard's final save preservesmodel.provider="custom"and thebase_url. Both the model-name and no-model-name branches are covered.What changed
hermes_cli/main.py—_model_flow_custom()now syncs the passedconfigdict after its own save (+15 lines)tests/hermes_cli/test_setup.py— Updated existing test to include the wizard's finalsave_config(config)call (the step that was previously untested), plus two new regression tests covering the Ollama and no-model-name scenariosTest plan
resolve_requested_provider()returns"custom"OPENROUTER_API_KEY+ custom endpoint → custom endpoint wins, base_url points to localhostmodel.providerandmodel.base_urlstill survive