Skip to content

Remote-hub ocx connect always times out downloading a large catalog (hard-coded 5s, no override, ~18KB base_instructions per model) #3305

Closed

Description

@tizerluo

Client or integration

Other

Area

Catalog / models

Summary

Remote-hub ocx connect deterministically times out downloading a large catalog: the client fetch has a hard-coded 5 s timeout with no flag or env override, and a full catalog (416 models ≈ 8.4 MB serialized, ~18 KB base_instructions per entry) needs ~6 s over a healthy ~1.4 MB/s tailnet link. The hub serves the same catalog locally in 0.15 s. connect then fails and rolls back (clean rollback, to be fair), so any hub with a large model menu is unreachable from slower links as shipped.

Reproduction

  1. Hub: opencodex 2.40.0, role=hub, catalog = 416 models ≈ 8.4 MB serialized (each entry carries a full base_instructions block of ~18 KB).

  2. Client: opencodex 2.40.0 on macOS on another continent, direct tailnet link ~1.4 MB/s (healthy link, 18 ms RTT, direct per tailscale status).

  3. Run:

    $ ocx connect <data-plane-url> --management-url <https-origin> --admin-token-stdin
    Error: The operation timed out.
    
  4. downloadClientCatalog() in src/client/connect.ts uses DEFAULT_TIMEOUT_MS = 5_000 (src/client/hub-client.ts) with no CLI flag or env override; over a ~1.4 MB/s link the 8.4 MB body needs ~6 s — past the cap.

  5. Workaround we used: disable liveModels on two aggregator providers to shrink the catalog below the budget (416 → 90 models, 8.4 MB → 1.9 MB, ~1.4 s transfer) — connect succeeded immediately.

Version

2.40.0

Operating system

macOS 26 (hub and client)

Provider and model

No response

Logs or error output

$ ocx connect <data-plane-url> --management-url <https-origin> --admin-token-stdin
Error: The operation timed out.

Screenshots and supporting files

No response

Redacted configuration

{ "runtimeRole": "hub", "providers": { "aggregator-example": { "liveModels": true } } }

Suggested fix

  1. Expose the client fetch timeout (flag or env) so slow links / large catalogs can be accommodated.
  2. Consider serving GET /v1/catalog gzipped — the JSON is highly compressible and this would shrink the footprint for every client.
  3. Consider documenting a catalog-size budget for remote clients, or warning on ocx sync when the persisted catalog exceeds it.

Related context: each routed model inheriting the full base_instructions template is what makes the per-entry cost ~18 KB; if that block were deduplicated or omitted for routed entries on the download path, catalogs would shrink by ~90%.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Re-filing of #3288 (auto-closed by the issue-quality bot because the original freeform body did not follow the Bug report form — content unchanged).

Activity

  1. added
    bugSomething isn't working
    catalogModel catalog, slugs, visibility, routed entries
    cliCLI, config inject, packaging flags
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Sep 2, 2026
  2. lidge-jun commented on Sep 2, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 72 / 80

    설명
    이 이슈는 원격 허브에 ocx connect를 하면 큰 카탈로그를 받는 단계에서 항상 타임아웃이 난다는 버그입니다. 지금 dev(HEAD 2e74a35d4)의 src/client/hub-client.ts를 보면 DEFAULT_TIMEOUT_MS = 5_000이고, fetchBounded가 AbortSignal.timeout으로 그 값을 요청 전체에 겁니다. downloadClientCatalog는 이 헬퍼로 GET /v1/catalog를 부릅니다. 헤더가 빨리 와도 본문을 다 읽기 전에 5초가 지나면 전체가 취소됩니다. 제보는 416모델, 직렬화 약 8.4 MB, 항목마다 base_instructions 약 18 KB, 대륙 사이 테일넷 약 1.4 MB/s에서 전송에 약 6초가 걸려 5초 한도를 넘는다고 합니다. 허브가 같은 카탈로그를 로컬에서 0.15초에 주는 것과 비교하면, 서버가 느린 게 아니라 클라 총시간 한도가 짧은 겁니다.

    실패하면 connect가 롤백합니다. 로컬 상태는 깨끗하게 돌아가지만, 모델 메뉴가 큰 허브는 느린 링크에서 아예 붙을 수가 없습니다. 제보 우회는 aggregator 두 곳의 liveModels를 꺼서 416→90, 8.4 MB→1.9 MB로 줄인 것입니다. 그건 제품을 고친 게 아니라 카탈로그를 깎아 한도 안으로 밀어 넣은 것입니다. src/client/connect.ts 411행 connect와 508행 syncConnectedClient(ocx sync) 둘 다 timeoutMs 없이 downloadClientCatalog를 부르므로, 지금 dev에서는 처음 연결과 이후 동기화가 같은 5초 절벽에 걸립니다. 상한 바이트 MAX_REMOTE_CATALOG_BYTES(256 MiB)는 넉넉하고, 문제는 시간입니다.

    Ready PR #3310이 이 이슈를 직접 고칩니다. 상수를 키우면 절벽만 옮겨질 뿐이라, 헤더 기한과 본문 기한을 나눴습니다. 헤더는 기존 5초(또는 --catalog-timeout)이고, 본문은 inactivity 타임아웃입니다. 바이트가 오는 동안은 불이 안 나고, 진짜로 멈춘 연결만 그 창 안에서 실패합니다. 구현은 이미 dev에 있는 src/lib/abort.ts의 clearableDeadline/idleDeadline과 readBoundedResponseBytes에 inactivity 옵션을 얹는 쪽입니다. 새 플래그 --catalog-timeout <seconds>(1–120)는 운영자가 창을 명시하고 싶을 때 씁니다. 문서(docs-site/src/content/docs/reference/cli/lifecycle.md, skills/ocx/references/05_remote_hub.md)도 같이 고쳤습니다.

    테스트는 핵심을 잘 잠급니다. tests/remote-catalog.test.ts는 로컬 스트리밍 응답이 2.6초 간격으로 청크를 보내 예전 5초 총예산을 넘기고도 성공하는 경우, 그리고 본문이 멈춘 연결이 짧은 inactivity 창 안에서 unreachable로 실패하는 경우를 봅니다. tests/client-connect.test.ts는 --catalog-timeout 0이 거절되는지를 봅니다. 제보의 8.4 MB 대륙 간 전송은 여기서 재현하지 않았고, PR 본문도 그 점을 솔직히 적었습니다. 그래도 “바이트가 오면 5초를 넘겨도 된다 / 멈추면 실패한다”는 계약은 focused 테스트로 증명됩니다. gzip과 base_instructions 중복 제거는 이슈가 제안한 추가 아이디어일 뿐, #3310이 고치는 실패 모드(총시간 5초)와는 별개입니다. 설계를 이 이슈에서 다시 열어 gzip을 같은 열차에 넣지 마세요.

    이 이슈는 #3288을 품질 봇이 폼 미준수로 닫은 뒤 같은 내용으로 다시 올린 것입니다. #3310 본문에 Closes #3305가 있고 Ready입니다. types.ts/config.ts 분할과도 무관하고, 현재 dev tip(#3302)과도 파일이 거의 겹치지 않습니다. 기본 inactivity 5초는 바이트가 리셋하므로 제보의 1.4 MB/s 전송은 플래그 없이도 통과해야 합니다. 플래그는 정말 느린 구간이나 운영자가 창을 늘리고 싶을 때의 탈출구입니다. gzip·카탈로그 크기 예산·ocx sync 경고는 후속으로 남겨도 이 이슈는 닫혀야 합니다.

    라인 31 - DEFAULT_TIMEOUT_MS = 5_000. 지금 dev에서는 헤더+본문 전체에 적용되어 큰 카탈로그가 항상 실패한다
    라인 92 - fetchBounded가 AbortSignal.timeout을 요청 전체에 건다. 본문 전송이 5초를 넘기면 헤더가 빨라도 취소된다. #3310이 헤더 전용으로 나눈다
    라인 411 - src/client/connect.ts connect 경로가 timeoutMs 없이 카탈로그를 받는다. #3310이 catalogTimeoutMs를 넘긴다
    라인 508 - syncConnectedClient도 같은 다운로드를 쓴다. #3310 기본 inactivity가 들어가면 sync도 같은 절벽에서 벗어난다. 플래그는 connect 전용이다
    경로 src/lib/bounded-body.ts readBoundedResponseBytes - 지금 dev에는 바이트 상한만 있고 inactivity가 없다. #3310이 청크마다 기한을 리셋한다
    경로 #3310 - Ready PR. Closes #3305. gzip/base_instructions 축소는 이 PR에 없고 후속이다

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

    • fix(connect): bound the catalog download by inactivity, not total duration #3310 CI가 초록이면 이 이슈를 그 PR로 바로 닫을지. gzip·카탈로그 크기 예산을 같은 이슈에 남겨 두지 말지
    • ocx sync에도 --catalog-timeout을 노출할지, connect 기본 inactivity만으로 충분한지(이번 PR 범위 밖)
    • 카탈로그 gzip 또는 routed 항목의 base_instructions 생략은 별도 이슈로 열지. 이 버그픽스 열차에 태우지 말 것

    너의 추천
    #3310을 머지하고 이 이슈를 그 PR로 닫으세요. 상수를 60초로 올리는 식의 절벽 이동은 하지 마세요. gzip과 항목당 18 KB 지시는 카탈로그 크기 후속 이슈로 분리하세요. 설계를 이 이슈에서 다시 열지 마세요.

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

  3. lidge-jun commented on Sep 2, 2026

    @lidge-jun
    Owner

    Fixed on dev in PR #3310.

    You were right that the 5 second budget was the bug, but raising the constant would only have moved the cliff — no fixed total duration is correct for an unknown link speed, and the next person with a slower link or a bigger catalog hits it again.

    The header deadline and the body deadline are now separate, and the body deadline is an inactivity timeout: it cannot fire while bytes are still arriving, so a 6-second (or 60-second) download over a healthy link succeeds, while a genuinely stalled connection still fails inside the bound rather than hanging. --catalog-timeout <seconds> (1–120) is there when you want to be explicit.

    Your numbers made this straightforward — 416 models at ~8.4 MB against a ~1.4 MB/s link, with the hub serving the same catalog locally in 0.15 s, isolated it to the transfer rather than to catalog assembly. One caveat worth stating: your intercontinental transfer was not reproducible here, so the regression holds a real local streaming response open past the old budget instead.

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 flagsproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions