Skip to content

[Bug]: Custom provider baseUrl with trailing slash doubles the model-discovery path (/v1//models) #4724

Closed

Description

@chaseyoungcn

Client or integration: OpenCodex dashboard

Area: Catalog / models

Summary

For custom (non-registry) providers whose baseUrl ends with /, model discovery requests a doubled path (https://gateway.example.com/v1//models). Gateways that treat the doubled path as a distinct route reject it -- I observed HTTP 403 on a custom OpenAI-compatible gateway -- so discovery fails and the catalog silently falls back to the configured models.

The send paths were already normalized in #1582 (openaiChatCompletionsUrl) and by openaiResponsesUrl, but discovery was not:

  • src/oauth/index.ts: buildModelsRequest() appends the endpoint verbatim (line ~1231 on main @ e4a8539): ${effectiveProvider.baseUrl}/models
  • src/providers/model-discovery.ts: resolveProviderModelDiscoveryUrl() returns that default URL unchanged when the provider has no registry spec -- i.e. exactly for custom providers.

Registry providers that carry a spec.path are unaffected, because new URL(spec.path, base) collapses the doubled slash; only custom providers get the doubled path.

Reproduction

  1. Add a custom provider whose baseUrl ends with a slash (see redacted config below).
  2. Trigger discovery: open the dashboard, or run ocx sync.
  3. The discovery request goes to https://gateway.example.com/v1//models; the gateway answers 403; the provider's discovery status becomes:
{"status":"failed","reason":"http","httpStatus":403}

and no live models appear.

Direct probes against the same gateway with the same key:

Request Result
GET /v1/models 200 (model list)
GET /v1//models 403

Minimal programmatic repro (repo root):

./node_modules/bun/bin/bun.exe -e '
const { buildModelsRequest } = await import("./src/oauth/index.ts");
const p = (baseUrl) => ({ adapter: "openai-responses", baseUrl, authMode: "key" });
console.log(buildModelsRequest(p("https://gateway.example.com/v1/"), "KEY", "custom").url);
console.log(buildModelsRequest(p("https://gateway.example.com/v1"), "KEY", "custom").url);'
# prints:
#   https://gateway.example.com/v1//models
#   https://gateway.example.com/v1/models

Version

2.56.0 -- also present on current main (e4a8539).

Operating system

macOS 15 (Apple silicon); also observed on Linux x86_64.

Provider and model

Custom OpenAI-compatible gateway using the openai-responses adapter (details redacted).

Logs or error output

Provider discovery status from the management API (/api/providers):

{"status":"failed","reason":"http","httpStatus":403}

Redacted configuration

{
  "providers": {
    "<custom-provider>": {
      "adapter": "openai-responses",
      "baseUrl": "https://gateway.example.com/v1/",
      "authMode": "key"
    }
  }
}

Suggested fix

Normalize the discovery join the same way as the send-path helpers -- trim trailing slashes (and accept a pasted /models) before appending /models, e.g. by reusing a shared helper in buildModelsRequest / resolveProviderModelDiscoveryUrl. Adding discovery cases to tests/url-normalization.test.ts would lock it in.

Workaround today: configure baseUrl without the trailing slash.

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
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    catalogModel catalog, slugs, visibility, routed entries
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Sep 15, 2026
  2. lidge-jun commented on Sep 15, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 74 / 80

    이슈 #4724는 커스텀(비레지스트리) 프로바이더의 baseUrl 끝에 슬래시(/)가 붙어 있을 때, 모델 디스커버리만 /v1//models처럼 슬래시가 두 번 붙는 버그를 보고한다. 지금 dev HEAD(45cfb04e9, package 2.57.0)에서도 같은 모양이 남아 있다. 보내기(send) 쪽은 이미 정규화되어 있다. openaiChatCompletionsUrl(src/adapters/openai-chat-url.ts)과 openaiResponsesUrl(src/adapters/openai-responses-url.ts)은 끝 슬래시를 먼저 지운 뒤 엔드포인트를 붙인다. 그런데 디스커버리 기본 경로는 그대로 이어 붙인다. buildModelsRequest의 일반 OpenAI 호환 분기(약 1231행)가 ${effectiveProvider.baseUrl}/models를 만들고, resolveProviderModelDiscoveryUrl은 레지스트리 spec이 없으면 그 defaultUrl을 그대로 돌려준다. 그래서 레지스트리 spec.path가 있는 프로바이더는 new URL(spec.path, base)가 슬래시를 합쳐 주지만, 대시보드/설정에 직접 넣은 커스텀 게이트웨이만 /v1//models로 나간다. 제보자처럼 그 경로를 403으로 거절하는 게이트웨이에서는 디스커버리가 failed/http로 떨어지고, 카탈로그는 설정에 적힌 정적 모델로만 조용히 내려앉는다. 보내기는 되고 목록만 비는 asymmetri가 된다. Antigravity CCA 분기는 이미 baseUrl.replace(/\/+$/, "")로 끝 슬래시를 지운다(약 1206행). 같은 규칙이 일반 /models 기본값에는 없다. 이슈가 가리킨 tests/url-normalization.test.ts 이름은 지금 트리에 없고, 가까운 자리는 tests/config/url-normalization.test.ts와 tests/adapters/openai/openai-chat-url.test.ts다. 재현 스크립트와 표(정상 /v1/models vs /v1//models 403)가 분명해서, 좁은 정규화 패치 하나로 닫기 좋은 버그다. 현재 dev 방향(카탈로그/커스텀 모델·UA 패스스루·벌크 refresh fence)과도 맞물린다. 타입/config 분할 캠페인에 무효화될 경로가 아니다.

    라인 1231 (src/oauth/index.ts buildModelsRequest 일반 분기) - baseUrl 끝 슬래시를 지우지 않고 /models를 이어 붙여 커스텀 프로바이더에서 /v1//models가 된다
    resolveProviderModelDiscoveryUrl (src/providers/model-discovery.ts) - 레지스트리 spec이 없으면 defaultUrl을 그대로 반환해서, 위 이중 슬래시를 고치지 않는다
    라인 1216 (buildModelsRequest ai-studio 분기) - ${effectiveProvider.baseUrl}/v1beta/models?...도 끝 슬래시가 있으면 같은 이중 슬래시 위험이 있다(Antigravity만 trim)
    tests/url-normalization.test.ts (이슈 제안 경로) - 현재 dev에는 없고, 디스커버리 케이스 추가는 tests/config/url-normalization.test.ts 또는 oauth/discovery 단위 테스트 쪽이 맞다
    이슈 본문 Suggested fix - send 헬퍼와 같은 trim(+ 붙여 넣은 /models 허용)을 buildModelsRequest/resolveProviderModelDiscoveryUrl에 공유 헬퍼로 두는 방향이 dev 코드와 잘 맞는다

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

    • trim만 할지, send 쪽처럼 붙여 넣은 /models·/chat/completions까지 벗겨낼지
    • 고칠 자리를 buildModelsRequest 기본 URL 생성으로만 둘지, resolveProviderModelDiscoveryUrl의 no-spec 경로에서도 한 번 더 정규화할지
    • ai-studio(1216)까지 같은 패치에 넣을지, OpenAI 호환 /models만 먼저 막을지

    너의 추천
    이슈는 유효하다. PR이 아직 없으니, buildModelsRequest 일반 분기에서 Antigravity와 같이 baseUrl 끝 슬래시를 지운 뒤 /models를 붙이고(가능하면 작은 공유 헬퍼), 커스텀 baseUrl trailing-slash 케이스를 테스트로 잠근 뒤 CI 그린이면 머지하면 된다. 임시 우회는 baseUrl에서 끝 /를 빼는 것. 중복 이슈/무효 PR은 현재 안 보인다. close-don't-rebase 해당 없음.

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

  3. added a commit that references this issue on Sep 16, 2026
  4. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    Owner

    Landed via #4773 at 6a8f754

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

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entrieslanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions