Repository navigation
[Bug]: client config exports ignore configured model aliases (alias_of reaches /v1/models but not Hermes / Command Code exports) #7703
Description
Client or integration
Other
Area
Catalog / models
Summary
Client config exports (ocx export --client hermes, --client commandcode, and the managed Connect integrations that reuse them) ignore configured model aliases. A model alias set with ocx alias set <provider>/<model> <alias> is visible in GET /v1/models as alias_of, but every exported client config keys the model by its routed slug instead. A user who names models with aliases therefore sees one set of names in Codex (display_name), another in /v1/models, and the raw routed slug in Hermes and Command Code.
Different clients need different spellings, so I am not asking for "always use the alias". I am asking whether the export should honor configured aliases, with an explicit per-integration choice.
Reproduction
ocx alias set command-code/reflection/beam:free CC-beam-freeocx alias list --json→"reflection/beam:free": {"alias": "CC-beam-free", "source": "user"}GET /v1/modelsrow →{"id": "command-code/reflection/beam:free", "alias_of": "CC/CC-beam-free"}ocx export --client hermes --json→ model keycommand-code/reflection-beam:freeocx export --client commandcode --json→ model keycommand-code/reflection-beam:free- Codex catalog (
~/.codex/opencodex-catalog.json) →slug: command-code/reflection-beam:free,display_name: CC/CC-beam-free
Actual behavior
The alias reaches /v1/models (alias_of) and the Codex picker (display_name), but never the exported client configs.
Expected behavior
Exports can key a model by its configured alias where that client wants alias names, while the router keeps accepting every spelling (it already does: CC-beam-free, command-code/reflection-beam:free and command-code/reflection/beam:free all return 200 on /v1/chat/completions, and CC-beam-free on /v1/messages).
Root cause (as far as I can read it)
model-rows.ts builds export rows with namespaced = catalogModelSlug(m), and catalogModelSlug is model.alias ?? routedSlug(provider, id) (codex/catalog/parsing.ts:922). CatalogModel.alias is only populated for combos (codex/catalog/aggregation.ts:195). Configured model aliases are resolved only when /v1/models is served (server/index/serve-options.ts:1287, alias_of). So the export path never sees them.
Impact
Anyone who standardizes model names with aliases has to run an external writer per client to get those names into the client config. That writer then puts the native integration into conflict / foreign-edit, so the native refresh can no longer manage the file.
Proposed shape (open to the maintainers' preference)
A per-integration naming option, default unchanged so no existing export changes silently:
routed(current behavior, default)alias— use the configured model alias when one exists, fall back to the routed slugraw— the/v1/modelsidas published
For example ocx integration client enable --client hermes --model-names alias, persisted with the integration record. Hermes users who read names in a picker would choose alias; clients that store the raw id would choose raw.
I am happy to send a PR against dev with focused regression tests if this direction is acceptable, and to adjust to whatever interface you prefer.
Version
2.92.0
Operating system
Linux (Ubuntu, x64), npm global install, systemd user service
Provider and model
Any provider with a configured model alias (example: command-code/reflection/beam:free → CC-beam-free)
Logs or error output
ocx alias list --json -> "reflection/beam:free": {"alias": "CC-beam-free", "source": "user"}
GET /v1/models -> {"id": "command-code/reflection/beam:free", "alias_of": "CC/CC-beam-free"}
ocx export --client hermes -> "command-code/reflection-beam:free"
ocx export --client commandcode -> "command-code/reflection-beam:free"
ocx integration client status --client hermes -> state: conflict, reason: foreign-edit
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
rrmlima commentedon Oct 25, 2026
Small correction to the root-cause pointer above: on
devthealias_ofprojection is atsrc/server/index/serve-options.ts:1301(I cited 1287 from the 2.92.0 tarball). The rest of the analysis still matchesdev:model-rows.ts:455builds export rows withcatalogModelSlug(m),codex/catalog/parsing.ts:922ismodel.alias ?? routedSlug(...), andCatalogModel.aliasis only set for combos incodex/catalog/aggregation.ts:195.Prior art for an opt-in in this area: #7173 / #7220 added
preferModelAliasIdsfor/v1/models. It does not reach client exports, which is the gap here.Ingwannu commentedon Oct 25, 2026
The distinction between a display alias, a published
/v1/modelsID and an export's routable model key matters here.preferModelAliasIdson the models endpoint does not by itself change client exports. An external writer also cannot safely share ownership of a file the managed integration fingerprints.A small opt-in export proposal is reasonable to review, with the existing spelling remaining the default; the per-client interface and persisted option still need the owner's product decision. Please start with Hermes/Command Code fixtures and the existing shared model-row builder rather than separate alias lookup implementations in each writer. Demonstrate that every emitted ID resolves to the same selected provider/model, including aliases with provider prefixes, collisions, unsafe selector characters, missing/renamed aliases and combo aliases. A display label is not automatically a safe request identifier. Keep a managed refresh idempotent, preserve foreign-edit refusal, and distinguish an export preview from a real client-file write.
The three proposed mode names are not yet an accepted API. In particular, define “raw” relative to the endpoint's alias preference so a later catalog refresh cannot silently change an existing connection's IDs. A concrete before/after export plus that collision/fallback contract should make the owner decision straightforward. No live client configuration change is needed for this design review.
rrmlima commentedon Oct 25, 2026
Thanks, @Ingwannu. Here is the before/after and the resolution contract you asked for, from synthetic fixtures run against
devat8ebe46b20. No live client config, no network, no real catalog. Legend: tested = produced by running repo code; proposed = what an opt-in mode would emit (exporter and router were run on the key, the mode itself is not implemented).Fixtures: provider
acme(provider aliasAC) withvendor/model-x→ aliasAC-model-x,model-y:free→AC-model-y-free,deep/nested/model-z(no alias); providerbetawithmodel-x(same basename, no alias); comboAC-combo→acme/vendor/model-x.Before/after export key (tested, real
toExportModel→ Hermes / Command Code exporters)previewRouteModel(key)acme/vendor/model-xacme/vendor-model-xAC/AC-model-xacme/vendor/model-x(explicit-provider-namespace)acme/model-y:freeacme/model-y:freeAC/AC-model-y-freeacme/model-y:free(explicit-provider-namespace)acme/deep/nested/model-zacme/deep-nested-model-zcombo/AC-comboAC-comboEvery current and proposed key resolved to the same provider/model as the full id (tested). The proposal emits the provider-qualified form (
AC/AC-model-x) rather than the bare alias, because:Cases that break a bare alias (tested)
AC-model-xset on bothacmeandbetasurvives load (sanitizeAliasesForLoadonly checks within a provider), and the router then refuses the bare name:model alias 'AC-model-x' is ambiguous: AC/AC-model-x, beta/AC-model-x. The qualified form stays unambiguous.AC-model-xsaved in a client becomesNo provider configured for model. The qualifiedAC/AC-model-xstill parses, but as an explicit namespace it routes to a non-existent upstream idacme/AC-model-x, which is worse than an error. So a saved alias key needs a defined fallback, not silent pass-through.AC/model,AC:model,AC model,AC#model,-AC-model) are rejected byMODEL_ALIAS_PATTERNand removed at load, so they can never be emitted.Relation to
preferModelAliasIds(tested)With the preference on,
/v1/modelspublishesAC-model-x/AC-model-y-freeas ids (bare), and leaves unaliased models unchanged. "Raw" therefore cannot mean "whatever/v1/modelsreturns", or flipping the preference would silently change an existing connection's ids.Proposed contract (owner decision, not an accepted API)
model-rows.ts/toExportModel), reusingeffectiveModelAliases, not per-writer lookups.preferModelAliasIdschange cannot switch an existing connection's spelling.ocx export) shows the result without writing; managed refresh stays idempotent and keeps foreign-edit refusal.Open questions for @lidge-jun: whether a refresh should rewrite a key whose alias was removed (back to the routed slug) or leave it and report; and whether combos are in scope for a first version.
One observation, not part of this issue: the alias PUT routes refresh the Codex catalog but not owned client integrations (
model-routes.ts:348/:362vsconvergeVisibleCatalogsat:202-212), so an alias change does not reach a managed client until the nextocx sync.Ingwannu commentedon Oct 25, 2026
This supplies the requested before/after and stale-alias contract, with a useful distinction between executed exporter/router fixtures and the unimplemented opt-in mode. I have not independently rerun your fixture script, so its results remain reporter-supplied evidence.
The qualified-namespace counterexample makes the remaining acceptance condition concrete: checking a proposed key at export time does not make a previously saved key safe after alias removal. An accepted design must either migrate the owned connection back to its stable routed slug with the normal foreign-edit protection, or reject/report the stale selector before it can become an upstream model id. It must not silently pass it through. Freeze the selected spelling policy per connection, independent of
preferModelAliasIds, as proposed.The per-client mode/persistence and removal behavior remain the project owner's product decision; no new configuration or command is accepted by this triage reply. Keep this issue open. The alias-PUT refresh observation is a separate convergence claim and should be proven through the managed-refresh seam rather than treated as coverage from this export-key fixture. No live client files or configuration were changed.
lidge-jun commentedon Oct 28, 2026
Owner decision recorded: accept an opt-in per-integration
aliasnaming mode for client exports. The default stays the routed slug. A managed refresh rewrites a key whose alias was removed back to the routed slug, with the normal foreign-edit protection. Combos are out of scope for the first version.Implementation is deferred to a follow-up change rather than this release window. The agreed contract for that change, from this thread:
preferModelAliasIds.AC/AC-model-x) is emitted only when the router resolves it to the same provider and model. Otherwise the routed slug is used, including for collisions, unsafe or missing aliases, and combos.ocx export --clientwith the option is a preview and never writes. Refresh is idempotent and keeps foreign-edit refusal. A removed alias falls back to the routed slug, and a renamed alias moves to the new validated key.Leaving this issue open until that change lands.
feat(integrations): add opt-in alias naming for Hermes and Command Co…
lidge-jun commentedon Oct 28, 2026
Implemented on dev by #7867 (3b8414d), following the owner decision above. Hermes and Command Code accept an opt-in alias naming mode: --model-names alias|routed on ocx export --client, integration client enable and integration client preview, and modelNames on the matching management API requests. The default stays the routed slug. The mode is stored on the integration record, and omitting it on a later apply keeps the stored mode; it does not follow preferModelAliasIds. A provider-qualified alias is emitted only when the router resolves it to the same provider and model; collisions, unsafe or missing aliases and combos keep the routed slug. ocx export with the option is a preview and never writes. Managed refresh moves a renamed alias to its new validated key and rewrites a removed alias back to the routed slug, with foreign-edit refusal unchanged.