Repository navigation
[Bug]: ocx opencode reads the management route GET /api/models with the data-plane admission key and exits 401 #4315
Description
Client or integration
Other (the ocx opencode launcher for the OpenCode CLI)
Area
CLI
Summary
ocx opencode reads the model catalog from GET /api/models with the data-plane admission key. That route is part of the management API, which requireManagementAuth (src/server/management-auth.ts:539, gated at src/server/index.ts:1289) admits only with the admin credential. On a hardened install the proxy therefore answers 401 {"error":"opencodex admin token required"}, the launcher prints ❌ Could not fetch the model catalog from the proxy: … and returns 1, and OpenCode is never launched.
Verified against a live 2.51.0 proxy on 127.0.0.1:10100 with an admin-api-token present:
$ curl -s -o /dev/null -w '%{http_code}\n' -H 'X-OpenCodex-API-Key: <data-plane admission key>' http://127.0.0.1:10100/api/models
401
{"error":"opencodex admin token required"}
$ curl -s -o /dev/null -w '%{http_code}\n' -H 'X-OpenCodex-API-Key: <admin token from ~/.opencodex/admin-api-token>' http://127.0.0.1:10100/api/models
200
Every other headless management caller already sends the admin credential — runningProxyUpdateHeaders in src/oauth/login-cli.ts, src/cli/claude.ts, and the GUI. cmdOpencode is the outlier: it calls fetchOpencodeProxyModels(live, apiKey) where apiKey = opencodeApiKey(startupConfig) resolves the admission key that is handed to the child OpenCode process.
Expected: the launcher authenticates its one management read with the configured admin token, keeping the admission key for the child process (and as a fallback for a host that truly has no admin credential).
Reproduction
- Use a hardened install where
ocx starthas minted~/.opencodex/admin-api-token. - With the proxy running, execute
ocx opencode. - Observe
❌ Could not fetch the model catalog from the proxy:and exit code 1; OpenCode never starts.
Code path: src/cli/opencode.ts — const apiKey = opencodeApiKey(startupConfig); then fetchOpencodeProxyModels(live, apiKey).
The same request with the admin token returns 200 and the catalog, which is what the launcher needs.
Version
@bitkyc08/opencodex 2.51.0 installed; the call site is unchanged on dev at 7a0513c (2.52.0).
Operating system
macOS 26.6.2 arm64 (proxy on loopback, port 10100).
Provider and model
Not provider-specific. The failing request is the management catalog read performed by the ocx opencode launcher.
Logs or error output
$ ocx opencode
❌ Could not fetch the model catalog from the proxy: GET /api/models responded 401: {"error":"opencodex admin token required"}Screenshots and supporting files
A fix with regression coverage is linked below as a pull request.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, personal data, local paths, session IDs, and request IDs.
Activity
lidge-jun commentedon Sep 12, 2026
리뷰 · 우선순위 60 / 80
이 이슈는 hardened 프록시에서
ocx opencode가 모델 카탈로그를 못 읽어 종료 코드 1로 죽는 문제입니다. 지금dev(8fc63277d, #4321 이후)에서도 같은 구조입니다. 런처src/cli/opencode.ts의cmdOpencode가fetchOpencodeProxyModels(live, opencodeApiKey(...))로GET /api/models를 치는데, 서버는src/server/index.ts에서url.pathname.startsWith("/api/")이면requireManagementAuth를 탑니다. 데이터 플레인 입장키는 여기서 거절되고, 응답은opencodex admin token required입니다. 자식 OpenCode가 대화할 때 쓰는 키와, 런처가 관리 목록을 읽을 때 써야 하는 키가 원래 다른 층입니다.재현 설명도 코드와 맞습니다. 같은 관리 읽기를 이미 올바르게 하는 호출자는
runningProxyUpdateHeaders(src/oauth/login-cli.ts)와 Claude 런처(src/cli/claude.ts)이고, 둘 다configuredAdminToken(src/lib/admin-secrets.ts: envOPENCODEX_ADMIN_AUTH_TOKEN또는admin-api-token파일)을 씁니다. OpenCode 런처만 입장키를 그대로 넣은 구멍입니다./v1/models같은 데이터 플레인 목록 API와/api/models관리 목록을 섞어 생각하면 이 버그가 다시 납니다.영향 범위는 "OpenCode를 ocx로 띄우는 사람 + admin 토큰이 켜진 프록시"입니다. 키풀(#4292)이나 Z.AI Responses 저장(#4321)과는 별개입니다. 고치지 않으면 OpenCode 통합 자체가 hardened 기본 설정에서 시작조차 못 합니다.
이미 수정 PR #4317이 열려 있습니다.
opencodeManagementToken으로 관리 토큰을 고르고 카탈로그 fetch에만 쓰며, 자식 env의 입장키는 유지합니다. 테스트도 토큰 우선순위와 헤더 assertion을 넣었습니다. 이슈 본문의 before/after(입장키 401 / admin 200)와 PR 설명이 일치합니다.src/cli/opencode.ts(fetchOpencodeProxyModels호출부) - 지금도 HEAD에서는 입장키를 그대로 넘깁니다. #4317이 랜딩되기 전에는 재현이 그대로입니다.src/server/index.ts(/api/*+requireManagementAuth) - 서버 쪽이 잘못된 것이 아닙니다. 관리 게이트가 의도대로 막은 결과입니다. "서버에서 입장키도 받게" 완화하는 방향은 보안 모델을 깨므로 비추천입니다.configuredAdminToken/ 다른 런처 - 수정 방향의 정답 패턴이 이미 레포 안에 있습니다. 새 설계보다 런처 정렬이 맞습니다.중복 - #4315와 같은 증상을 다른 이슈로 열린 최근 항목은 스캔 범위에서 보이지 않았고, 수정은 #4317 하나로 묶는 편이 맞습니다.
메인테이너의 판단이 필요한 지점
너의 추천
서버를 느슨하게 만들지 말고 #4317을 랜딩해 이슈를 닫으세요. 재현·원인·수정이 이미 한 줄로 연결돼 있어 추가 조사 이슈로 둘 필요가 없습니다. types/config 스플릿과 무관합니다.
이 댓글은 grok-bot이 작성했습니다
Ingwannu commentedon Sep 12, 2026
Confirmed the launcher credential mismatch and reviewed the proposed fix in #4317. I requested changes on head 27f577a because the new admin credential still travels through the old global-fetch destination path; the existing proxy/redirect and local-destination boundaries must be fixed before landing. The direct-local HTTP helper avoids proxy routing but is not itself a loopback allowlist, so both transport and destination checks matter. The child still needs the separate admission credential. @lidge-jun keep this issue open until the corrected exact-head PR is verified; no production configuration or tokens were changed.
cortes-felipe commentedon Sep 12, 2026
Corrected PR #4317 is up at
d8555e412, rebased ontodevdcd13b435. What changed against the review on this issue:OPENCODEX_ADMIN_AUTH_TOKEN, then the hardenedadmin-api-tokenfile), but the destination is now refused unless it is loopback, and that refusal happens before any request is built. The origin comes fromlocalManagementOrigin, andprobeHostnamekeeps the supported wildcard/IPv4/IPv6 spellings dialing127.0.0.1;directLocalHttpFetchrather than globalfetch: no proxy-environment routing, no redirect following, proxy headers dropped. The destination check is a separate caller-level step, since that helper is a transport and not a loopback allowlist;/api/modelsadded to the read allowlist (route registeredmutates: false; server still admits only a bodyless, query-freeGETbound to the attested pid and port, replay-protected), so no reusable credential is sent at all. An older proxy, or a hub ingress on a different port, falls back to the loopback token read;buildOpencodeEnv, and the tests now assert that on the spawned env and the inline config — the earlier claim that an admin env variable never reaches the child was wrong (that inheritance predates this PR) and the description is corrected;structure/runtime.md,structure/config.md,structure/clients/claude-desktop.md,structure/ops/docs-and-release.md, plus the publicdocs-site/.../guides/opencode.mdrecord the management-credential versus admission-key contract.Verification on that head:
bun test tests/providers/opencode-cli.test.ts63 pass / 0 fail;bun test tests/server/server-management-auth.test.ts47 pass / 0 fail;bun run test:changed14,565 pass / 0 fail across 647 files;bun run typecheck,bun run structure:check,bun run privacy:scanclean.The PR stays in draft until the reviewer verifies this exact head, so this issue stays open until then.
lidge-jun commentedon Sep 13, 2026
Resolved on
devby #4402, landed today as part of a 36-pull-request batch.src/cli/opencode.tsresolvesconfiguredAdminToken()before callingfetchOpencodeProxyModels, soocx opencodereads the management routeGET /api/modelswith the admin token over loopback while the spawned child still receives the admission key.The admission-key fallback was intentionally dropped during review, so the hardened-proxy 401 in the report no longer occurs.
Verified against
origin/devat72601a173after the batch landed, rather than from the pull request description. Closing on that evidence; reopen if the behavior does not match on your setup.