Summary
After a router reload (POST /models/reload, or a preset/source change that triggers load_models()) that changes a model's aliases, get_meta() resolves the affected alias incorrectly:
- Removed alias → still resolves to the old model (should be not-found).
- Moved alias (from model
foo to model baz) → resolves to foo (the old owner), mis-routing the request to the wrong model.
has_model() is correct in both cases, so the two disagree after a reload — a code path that gates on has_model and one that resolves via get_meta will diverge.
Root cause
load_models() reload rebuilds each model's inst.meta.aliases but never reconciles the global alias_to_name map:
- The cleanup at
tools/server/server-models.cpp:595 only erases alias_to_name entries whose target model is gone (mapping.find(it->second) == end). It does not touch entries whose target still exists but no longer owns the alias.
- The re-parse loop at
:604-635 does inst.meta.aliases.clear() + re-inserts from the new preset, but writes nothing to alias_to_name.
So an alias removed-from or moved-between still-existing models leaves a stale alias_to_name[oldalias] -> oldname.
Why it mis-resolves (resolution ordering)
get_meta() (:760) resolves in this order:
- direct name in
mapping
alias_to_name.find(name) (:766) → returns that target's meta
- fallback scan of every model's
meta.aliases (:774)
Step 2 short-circuits on the stale entry before the authoritative step-3 scan runs. By contrast has_model() skips alias_to_name entirely and uses only the meta.aliases scan — which is why it stays correct and the two diverge.
Why not just drop alias_to_name
Per #135 (ecf2de428, fixing #122) the map exists specifically to resolve aliases when a loaded instance's meta.aliases are shadowed/lost — i.e. the meta.aliases scan is not always sufficient. So the fix is to keep alias_to_name in sync on reload, not remove it.
Repro
- Preset INI:
[foo] with alias = bar; start router.
- Edit preset: remove
bar from [foo] (or move alias = bar to a new [baz]).
POST /models/reload.
- Resolve the model by
bar (e.g. GET /models lookup / a request targeting bar): it still resolves to foo via the stale alias_to_name, instead of not-found (removed) or baz (moved). has_model("bar") disagrees.
Suggested fix
After the re-parse loop finishes rebuilding all meta.aliases, rebuild alias_to_name from scratch (clear + repopulate from each model's final aliases), or update it inside the re-parse loop (drop the model's old alias entries, add the new ones). Keep the existing dead-target cleanup. A regression test can extend test_router_reload_models to move an alias between two preset sections and assert resolution follows.
Severity
Medium — requires an alias-changing reload (a real ops action: editing the preset INI + reload), but then silently routes requests to the wrong model or produces get_meta/has_model inconsistency. Same alias-map-consistency class as #135 / #122.
Found via idle bug-hunt; verified against origin/ht (90ae01b7a). Happy to PR the reconcile fix — holding it until #148 lands to avoid conflicts on server-models.cpp.
Summary
After a router reload (
POST /models/reload, or a preset/source change that triggersload_models()) that changes a model's aliases,get_meta()resolves the affected alias incorrectly:footo modelbaz) → resolves tofoo(the old owner), mis-routing the request to the wrong model.has_model()is correct in both cases, so the two disagree after a reload — a code path that gates onhas_modeland one that resolves viaget_metawill diverge.Root cause
load_models()reload rebuilds each model'sinst.meta.aliasesbut never reconciles the globalalias_to_namemap:tools/server/server-models.cpp:595only erasesalias_to_nameentries whose target model is gone (mapping.find(it->second) == end). It does not touch entries whose target still exists but no longer owns the alias.:604-635doesinst.meta.aliases.clear()+ re-inserts from the new preset, but writes nothing toalias_to_name.So an alias removed-from or moved-between still-existing models leaves a stale
alias_to_name[oldalias] -> oldname.Why it mis-resolves (resolution ordering)
get_meta()(:760) resolves in this order:mappingalias_to_name.find(name)(:766) → returns that target's metameta.aliases(:774)Step 2 short-circuits on the stale entry before the authoritative step-3 scan runs. By contrast
has_model()skipsalias_to_nameentirely and uses only themeta.aliasesscan — which is why it stays correct and the two diverge.Why not just drop
alias_to_namePer #135 (
ecf2de428, fixing #122) the map exists specifically to resolve aliases when a loaded instance'smeta.aliasesare shadowed/lost — i.e. themeta.aliasesscan is not always sufficient. So the fix is to keepalias_to_namein sync on reload, not remove it.Repro
[foo]withalias = bar; start router.barfrom[foo](or movealias = barto a new[baz]).POST /models/reload.bar(e.g.GET /modelslookup / a request targetingbar): it still resolves tofoovia the stalealias_to_name, instead of not-found (removed) orbaz(moved).has_model("bar")disagrees.Suggested fix
After the re-parse loop finishes rebuilding all
meta.aliases, rebuildalias_to_namefrom scratch (clear + repopulate from each model's final aliases), or update it inside the re-parse loop (drop the model's old alias entries, add the new ones). Keep the existing dead-target cleanup. A regression test can extendtest_router_reload_modelsto move an alias between two preset sections and assert resolution follows.Severity
Medium — requires an alias-changing reload (a real ops action: editing the preset INI + reload), but then silently routes requests to the wrong model or produces
get_meta/has_modelinconsistency. Same alias-map-consistency class as #135 / #122.Found via idle bug-hunt; verified against
origin/ht(90ae01b7a). Happy to PR the reconcile fix — holding it until #148 lands to avoid conflicts onserver-models.cpp.