Skip to content

[Bug]: ocx sync emits duplicate catalog slugs when modelAliases differs from canonical form #4730

Closed

Description

@rrmlima

Client or integration

Codex CLI

Area

Catalog / models

Summary

ocx sync on 2.56.0 generated opencodex-catalog.json with 507 entries but only 72 unique slugs (e.g. CC-MiniMaxAI-MiniMax-M3 repeated 14x). I expected one entry per slug. The duplicates are byte-identical, so they are safe to drop — but the sync should never emit them.

Root cause (verified against pristine 2.56.0 tarball): my config uses the official modelAliases feature (providers.command-code.modelAliases = {"MiniMaxAI/MiniMax-M3": "CC-MiniMaxAI-MiniMax-M3", ...}, 17 entries). The alias form becomes the public slug via model.alias (catalogModelSlug in src/codex/catalog/parsing.ts:843: model.alias ?? routedSlug(provider, id)). The sync ALSO emits the canonical command-code/... form via routedSlug, and the merge (mergeCatalogEntriesFromObservedState in src/codex/catalog/build-entries.ts, key slugEquivalenceKey in src/providers/slug-codec.ts) never unifies them:

  • slugEquivalenceKey("CC-X") → ["exact","CC-X"] (no slash = exact key)
  • slugEquivalenceKey("command-code/...") → ["routed","command-code",...]

Three different keys for the same model = three rows.

Reproduction

  1. Config with provider command-code + modelAliases mapping Vendor/Model → CC-Vendor-Model (17 entries), plus provider alias: "CC".
  2. ocx sync --restart-codex && ocx sync-cache --restart-codex (config validates {"ok": true}).
  3. jq '.models|length' ~/.codex/opencodex-catalog.json → 507; jq '[.models[].slug]|unique|length' → 72.
  4. Note: ocx sync-cache alone kept 72/72; the explosion came from ocx sync. A later ocx sync on the same config stayed 72/72, so it is intermittent (possibly first sync after upgrade, or cache-state dependent).

Version

2.56.0 (@bitkyc08/opencodex, npm latest 2026-09-15)

Operating system

Ubuntu 24.04 (VPS, HestiaCP)

Provider and model

command-code provider (gateway), any model with a modelAliases entry

Logs or error output

# before dedup
$ jq '.models|length' ~/.codex/opencodex-catalog.json
507
$ jq '[.models[].slug]|unique|length' ~/.codex/opencodex-catalog.json
72
$ jq -r '.models[].slug' ~/.codex/opencodex-catalog.json | sort | uniq -c | sort -rn | head -3
     15 agy-gemini-3.8-flash
     14 agy-claude-sonnet-4-6
     14 CC-MiniMaxAI-MiniMax-M3
# entries are byte-identical per slug:
$ jq -c '[.models | group_by(.slug) | map(select(length>1)) | .[0][]] | unique | length'
1

Local validator correctly refused to publish: publish-catalog.sh: source-invalid (requires unique slugs).

Screenshots and supporting files

Sample pair for the same model (same display_name, description, context_window):

{"slug":"CC-MiniMaxAI-MiniMax-M3","display_name":"CC-MiniMaxAI-MiniMax-M3","description":"Routed via opencodex → command-code (command-code)."}
{"slug":"command-code/MiniMaxAI-MiniMax-M3","display_name":"CC-MiniMaxAI-MiniMax-M3","description":"Routed via opencodex → command-code (command-code)."}

Full 11 MB pre-dedup catalog available on request (507 entries).

Redacted configuration

{
  "providers": {
    "command-code": {
      "alias": "CC",
      "liveModels": true,
      "selectedModels": ["MiniMaxAI/MiniMax-M3", "deepseek/deepseek-v4-flash"],
      "modelAliases": {
        "MiniMaxAI/MiniMax-M3": "CC-MiniMaxAI-MiniMax-M3",
        "deepseek/deepseek-v4-flash": "CC-deepseek-deepseek-v4-flash"
      }
    }
  }
}

Proposed fix

In syncCatalogModels/retained-sync.ts, final dedup by slug after merge — the model.alias (user personalization, e.g. CC-X) wins over the canonical form when content is identical; warn with removed count. Preserves personalization, kills duplicates, single change point. Local test proving safe discard (507→72, 0 unique slugs lost, personalized content byte-identical) available as input for the PR.

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
    cliCLI, config inject, packaging flags
    on Sep 15, 2026
  2. lidge-jun commented on Sep 15, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 76 / 80

    이 이슈는 ocx sync가 카탈로그를 쓸 때, 같은 모델을 서로 다른 공개 슬러그로 여러 번 넣는 버그를 보고한다. 작성자 rrmlima는 2.56.0에서 opencodex-catalog.json이 507개 행인데 고유 슬러그는 72개뿐이었다고 했다. 중복 행은 바이트 단위로 같고, 로컬 publish-catalog.sh는 unique slug 규칙 때문에 source-invalid로 거절했다. 재현 설정은 providers.command-code.modelAliases로 MiniMaxAI/MiniMax-M3 → CC-MiniMaxAI-MiniMax-M3처럼 별칭을 주고, provider alias: "CC"까지 켠 경우다.

    지금 dev tip(45cfb04e9, package 2.57.0)의 코드를 보면 원인 설명이 맞다. src/codex/catalog/parsing.ts의 catalogModelSlug는 model.alias ?? routedSlug(provider, id)라서, 별칭이 있으면 공개 슬러그가 CC-… 같은 슬래시 없는 문자열이 된다. 그런데 sync 경로에서는 같은 모델의 정규 command-code/… 형태도 같이 들어올 수 있다. 합치는 쪽은 mergeCatalogEntriesFromObservedState(src/codex/catalog/build-entries.ts)이고, 키는 slugEquivalenceKey(src/providers/slug-codec.ts)다.

    여기가 핵심이다. slugEquivalenceKey는 슬래시가 없으면 ["exact","CC-X"]이고, 슬래시가 있으면 ["routed","command-code",…]다. 의도적으로 “별칭 문자열”과 “라우티드 슬러그”를 같은 모델로 묶지 않는다. 그래서 병합이 끝나도 CC-MiniMaxAI-MiniMax-M3와 command-code/MiniMaxAI-MiniMax-M3가 둘 다 남는다. 작성자가 본 것처럼 같은 표시 이름/설명을 가진 행이 여러 개 쌓이고, 한 번 ocx sync에서 터졌다가 이후 sync에서는 72/72로 안정된 것처럼 보이면, 관측 상태·캐시·이전 카탈로그 잔여가 섞인 간헐 재현과도 잘 맞는다. ocx sync-cache만으로는 안 터졌다는 점도, 전체 sync의 merge 입력이 더 넓다는 설명과 맞다.

    제안 수정(“최종적으로 exact slug로 한 번 더 dedup하고, 내용이 같으면 alias 쪽을 남긴다”)은 증상 차단으로는 빠르고 안전하다. 다만 근본은 “별칭이 붙은 모델의 정규 routed 행을 언제 빼고 언제 남길지”를 catalog 빌드/머지 한곳에서 정하는 일이다. 최종 slug 문자열 dedup만 하면, 내용이 살짝 다른 twin(표시명만 다름 등)이 오면 어느 쪽을 살릴지 또 정책이 필요하다. types.ts/config.ts 분할 캠페인과는 무관하고, 닫아 버릴 중복 이슈로 보이지도 않는다. 카탈로그/CLI 버그로 열어 두는 것이 맞다.

    경로/심볼로 보면 아래가 맞다.

    parsing.ts · catalogModelSlug - alias가 있으면 슬래시 없는 공개 슬러그가 된다. 이 동작 자체는 의도된 개인화다.
    slug-codec.ts · slugEquivalenceKey - 슬래시 유무로 exact/routed 키가 갈라져, alias↔canonical을 같은 모델로 보지 않는다. 작성자 분석과 일치한다.
    build-entries.ts · mergeCatalogEntriesFromObservedState - equivalence key로만 묶이므로 twin slug가 그대로 살아남을 수 있다.
    retained-sync.ts · syncCatalogModels - 최종 write 전에 exact-slug unique를 강제하지 않으면 publish validator와 어긋난 파일을 남긴다.
    재현/증상 - 507→72, byte-identical duplicates, sync-cache만으로는 안정. 간헐성은 캐시/관측 잔여 가설이 타당하다.

    메인테이너의 판단이 필요한 지점

    • 최종 방어를 sync 끝 exact-slug dedup으로 둘지, build/merge에서 alias가 있으면 canonical routed 행을 아예 안 넣을지
    • twin의 내용이 다를 때 alias 우선·canonical 우선·경고 후 둘 다 유지 중 어떤 정책이 맞는지
    • 회귀 테스트를 modelAliases + provider alias 조합으로 고정할지(작성자가 로컬 검증 데이터를 줄 수 있다고 함)

    너의 추천
    버그로 받고, 작은 PR을 dev로 받는 쪽을 권한다. 1) modelAliases가 있는 provider에서 sync 결과가 slug unique인지 실패하는 회귀 테스트부터 넣고, 2) sync 최종 단계에서 exact slug dedup(동일 내용이면 alias 슬러그 우선)으로 증상을 막은 뒤, 3) 가능하면 merge/build에서 alias↔routed twin이 생기지 않게 한곳에서 정리한다. 지금 tip 기준 재현만 확인되면 우선순위 높은 catalog 버그다. types/config 분할에 걸려 닫을 대상은 아니다.

    이 댓글은 grok-bot이 작성했습니다

  3. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    Owner

    Fixed on dev, unreleased at the time of writing.

    src/codex/catalog/aggregation.ts now skips a slug it has already emitted (if (seen.has(entry.slug)) continue, lines 233-244), and src/codex/catalog/retained-sync.ts:523 is the caller that consumes the de-duplicated list, so a modelAliases spelling that differs from the canonical form no longer produces a second row.

    Landed as fab7e427c7 (#4799), whose head had Cross-platform CI success in run 35084581608. It ships in 2.57.0. Reopen if you still see duplicate slugs after upgrading.

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 entriescliCLI, config inject, packaging flags

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions