Skip to content

[Bug]: client config exports ignore configured model aliases (alias_of reaches /v1/models but not Hermes / Command Code exports) #7703

Closed

Description

@rrmlima

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

  1. ocx alias set command-code/reflection/beam:free CC-beam-free
  2. ocx alias list --json → "reflection/beam:free": {"alias": "CC-beam-free", "source": "user"}
  3. GET /v1/models row → {"id": "command-code/reflection/beam:free", "alias_of": "CC/CC-beam-free"}
  4. ocx export --client hermes --json → model key command-code/reflection-beam:free
  5. ocx export --client commandcode --json → model key command-code/reflection-beam:free
  6. 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 slug
  • raw — the /v1/models id as 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

  1. added
    bugSomething isn't working
    catalogModel catalog, slugs, visibility, routed entries
    on Oct 24, 2026
  2. rrmlima commented on Oct 25, 2026

    @rrmlima
    ContributorAuthor

    Small correction to the root-cause pointer above: on dev the alias_of projection is at src/server/index/serve-options.ts:1301 (I cited 1287 from the 2.92.0 tarball). The rest of the analysis still matches dev: model-rows.ts:455 builds export rows with catalogModelSlug(m), codex/catalog/parsing.ts:922 is model.alias ?? routedSlug(...), and CatalogModel.alias is only set for combos in codex/catalog/aggregation.ts:195.

    Prior art for an opt-in in this area: #7173 / #7220 added preferModelAliasIds for /v1/models. It does not reach client exports, which is the gap here.

  3. Ingwannu commented on Oct 25, 2026

    @Ingwannu
    Owner

    The distinction between a display alias, a published /v1/models ID and an export's routable model key matters here. preferModelAliasIds on 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.

  4. rrmlima commented on Oct 25, 2026

    @rrmlima
    ContributorAuthor

    Thanks, @Ingwannu. Here is the before/after and the resolution contract you asked for, from synthetic fixtures run against dev at 8ebe46b20. 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 alias AC) with vendor/model-x → alias AC-model-x, model-y:free → AC-model-y-free, deep/nested/model-z (no alias); provider beta with model-x (same basename, no alias); combo AC-combo → acme/vendor/model-x.

    Before/after export key (tested, real toExportModel → Hermes / Command Code exporters)

    model current key (Hermes = Command Code) proposed alias key previewRouteModel(key)
    acme/vendor/model-x acme/vendor-model-x AC/AC-model-x acme/vendor/model-x (explicit-provider-namespace)
    acme/model-y:free acme/model-y:free AC/AC-model-y-free acme/model-y:free (explicit-provider-namespace)
    acme/deep/nested/model-z acme/deep-nested-model-z unchanged (no alias) same model
    combo/AC-combo AC-combo unchanged combo pick, not a single target

    Every 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)

    • Cross-provider duplicate: AC-model-x set on both acme and beta survives load (sanitizeAliasesForLoad only 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.
    • Renamed/removed alias: a bare AC-model-x saved in a client becomes No provider configured for model. The qualified AC/AC-model-x still parses, but as an explicit namespace it routes to a non-existent upstream id acme/AC-model-x, which is worse than an error. So a saved alias key needs a defined fallback, not silent pass-through.
    • Unsafe selector characters (AC/model, AC:model, AC model, AC#model, -AC-model) are rejected by MODEL_ALIAS_PATTERN and removed at load, so they can never be emitted.
    • Combos already export their alias; no change proposed.

    Relation to preferModelAliasIds (tested)
    With the preference on, /v1/models publishes AC-model-x / AC-model-y-free as ids (bare), and leaves unaliased models unchanged. "Raw" therefore cannot mean "whatever /v1/models returns", or flipping the preference would silently change an existing connection's ids.

    Proposed contract (owner decision, not an accepted API)

    1. Default stays exactly as today.
    2. An opt-in alias mode resolves through the shared model-row builder (model-rows.ts / toExportModel), reusing effectiveModelAliases, not per-writer lookups.
    3. Emit the provider-qualified alias only when that alias resolves (via the router) to the same provider/model; otherwise emit the routed slug for that model.
    4. The mode is recorded with the integration at connect time, so a later catalog refresh or a preferModelAliasIds change cannot switch an existing connection's spelling.
    5. Preview (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/:362 vs convergeVisibleCatalogs at :202-212), so an alias change does not reach a managed client until the next ocx sync.

  5. Ingwannu commented on Oct 25, 2026

    @Ingwannu
    Owner

    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.

  6. lidge-jun commented on Oct 28, 2026

    @lidge-jun
    Owner

    Owner decision recorded: accept an opt-in per-integration alias naming 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:

    • Hermes and Command Code only. Other exporters keep routed keys.
    • The naming mode is stored on the integration record when the connection is made. Omitting the option on a later apply keeps the stored mode. The mode does not follow preferModelAliasIds.
    • A provider-qualified alias (for example 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 --client with 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.

  7. lidge-jun commented on Oct 28, 2026

    @lidge-jun
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entries

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions