Repository navigation
Remote-hub ocx connect always times out downloading a large catalog (hard-coded 5s, no override, ~18KB base_instructions per model) #3305
Description
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
-
Hub: opencodex 2.40.0, role=hub, catalog = 416 models ≈ 8.4 MB serialized (each entry carries a full
base_instructionsblock of ~18 KB). -
Client: opencodex 2.40.0 on macOS on another continent, direct tailnet link ~1.4 MB/s (healthy link, 18 ms RTT,
directpertailscale status). -
Run:
$ ocx connect <data-plane-url> --management-url <https-origin> --admin-token-stdin Error: The operation timed out. -
downloadClientCatalog()insrc/client/connect.tsusesDEFAULT_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. -
Workaround we used: disable
liveModelson two aggregator providers to shrink the catalog below the budget (416 → 90 models, 8.4 MB → 1.9 MB, ~1.4 s transfer) —connectsucceeded 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
- Expose the client fetch timeout (flag or env) so slow links / large catalogs can be accommodated.
- Consider serving
GET /v1/cataloggzipped — the JSON is highly compressible and this would shrink the footprint for every client. - Consider documenting a catalog-size budget for remote clients, or warning on
ocx syncwhen 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
lidge-jun commentedon Sep 2, 2026
리뷰 · 우선순위 72 / 80
설명
이 이슈는 원격 허브에
ocx connect를 하면 큰 카탈로그를 받는 단계에서 항상 타임아웃이 난다는 버그입니다. 지금dev(HEAD2e74a35d4)의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.ts411행 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 분할과도 무관하고, 현재
devtip(#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.tsconnect 경로가timeoutMs없이 카탈로그를 받는다. #3310이catalogTimeoutMs를 넘긴다라인 508 -
syncConnectedClient도 같은 다운로드를 쓴다. #3310 기본 inactivity가 들어가면 sync도 같은 절벽에서 벗어난다. 플래그는 connect 전용이다경로
src/lib/bounded-body.tsreadBoundedResponseBytes- 지금dev에는 바이트 상한만 있고 inactivity가 없다. #3310이 청크마다 기한을 리셋한다경로 #3310 - Ready PR. Closes #3305. gzip/
base_instructions축소는 이 PR에 없고 후속이다메인테이너의 판단이 필요한 지점
ocx sync에도--catalog-timeout을 노출할지, connect 기본 inactivity만으로 충분한지(이번 PR 범위 밖)base_instructions생략은 별도 이슈로 열지. 이 버그픽스 열차에 태우지 말 것너의 추천
#3310을 머지하고 이 이슈를 그 PR로 닫으세요. 상수를 60초로 올리는 식의 절벽 이동은 하지 마세요. gzip과 항목당 18 KB 지시는 카탈로그 크기 후속 이슈로 분리하세요. 설계를 이 이슈에서 다시 열지 마세요.
이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 2, 2026
Fixed on
devin 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.