Repository navigation
[Bug]: a newer ocx restart respawns an older standalone proxy from its old installation #4522
Description
Client or integration
Codex CLI
Area
Service lifecycle
Summary
After an external version-manager upgrade, invoking ocx restart with the newer CLI can restart an older unsupervised proxy back into the older version instead of adopting the invoking CLI version.
An isolated reproduction started a 2.49.0 proxy from its versioned Mise installation, then invoked restart through the 2.53.0 CLI using the same temporary OpenCodex and Codex homes. The proxy PID changed, but /healthz still reported 2.49.0 and the replacement command line still referenced the 2.49.0 package tree.
This conflicts with the CLI description of restart as equivalent to stop plus ensure. A user who has already upgraded OpenCodex and runs the newly selected ocx restart reasonably expects the replacement to use that selected installation.
Reproduction
-
Keep OpenCodex 2.49.0 and 2.53.0 installed in separate version-manager directories.
-
Point
OPENCODEX_HOMEandCODEX_HOMEat temporary empty directories. -
Start the 2.49.0 CLI on an unused port:
<mise-data>/installs/opencodex/2.49.0/node_modules/.bin/ocx start --port 19149
-
Confirm
GET http://127.0.0.1:19149/healthzreports version 2.49.0. -
Using the same temporary homes, invoke restart through 2.53.0:
<mise-data>/installs/opencodex/2.53.0/node_modules/.bin/ocx restart
-
Confirm that the proxy PID changed but
/healthzstill reports 2.49.0. -
Inspect the replacement command line and observe that both the Bun runtime and OpenCodex entry point still come from the 2.49.0 installation.
The source explains the result: the newer CLI delegates restart to the live proxy through /api/system/restart. For an unsupervised process, that older proxy calls spawn(process.execPath, selfLaunchArgv(...)); selfLaunchArgv() uses the live process's own process.argv[1]. The replacement therefore inherits the old runtime and old source entry point.
Version
2.53.0 CLI restarting a live 2.49.0 standalone proxy. The same self-spawn implementation remains on current dev at df7dc1be530141c55bd10e70ebe79cb80914d98e (package version 2.54.0).
Operating system
Ubuntu 24.04 under WSL2
Provider and model
Not provider- or model-specific
Logs or error output
before: version=2.49.0, pid=<old-pid>
restart command: OpenCodex 2.53.0 CLI
after: version=2.49.0, pid=<new-pid>
replacement runtime: <mise-data>/installs/opencodex/2.49.0/.../bun
replacement entry: <mise-data>/installs/opencodex/2.49.0/.../src/cli/index.tsScreenshots and supporting files
The reproduction used an isolated temporary home and unused port. The temporary proxy was stopped after verification.
Redacted configuration
{}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Related: #3464 and #2898 made service definitions use stable launchers so a later supervisor restart adopts a version-manager upgrade. #4173 tracks broader coordinated update guarantees. This report is specific to an already-running unsupervised proxy accepting a restart from a newer CLI and relaunching its own older package tree.
The current workaround is an explicit ocx stop followed by ocx service start or ocx start from the new installation. A fix could make the restart request carry an identity-verified replacement launcher selected by the invoking CLI, or detect version skew and refuse with the exact stop/start workaround instead of reporting a successful restart into the old version.
Activity
lidge-jun commentedon Sep 13, 2026
리뷰 · 우선순위 74 / 80
이 이슈는 버전 매니저로 OpenCodex를 올린 뒤, 새 CLI에서
ocx restart를 실행해도 이미 떠 있던 무감독(standalone) 프록시가 자기 옛 설치 트리로 다시 살아나는 버그입니다. 재현은 분명합니다. Mise에 2.49.0과 2.53.0을 나란히 두고 2.49.0으로 start한 다음, 같은 임시 홈에서 2.53.0ocx restart를 치면 PID는 바뀌지만/healthz버전과 교체 프로세스의 Bun/엔트리는 계속 2.49.0입니다. 사용자는 restart를 stop+ensure와 같다고 읽는데, 실제로는 “옛 바이너리 자기복제”가 성공으로 보고됩니다.현재
dev(HEADdf7dc1be5, 2.54.0) 코드 경로도 보고와 맞습니다. CLIrestart는 살아 있는 프록시에게POST /api/system/restart를 맡깁니다(src/cli/dispatch.ts,src/cli/system-restart-client.ts). 무감독 교체 쪽src/server/management/system-restart.ts의spawnDetachedStart는spawn(process.execPath, selfLaunchArgv(["start", ...]))로 자식을 띄웁니다.src/lib/self-launch-argv.ts의selfLaunchArgv는 현재 프로세스의process.argv[1](또는 standalone 모드)을 그대로 쓰므로, 요청을 받은 쪽이 옛 설치면 교체도 옛 설치입니다. 최근dev에 올라온 desktop-app restart/#4510/#4520 작업은 Codex 데스크톱 재시작 축이고, 이 무감독 프록시 self-spawn 경로와는 다릅니다. #3464/#2898이 서비스 정의의 stable launcher 문제는 줄였지만, 이미 떠 있는 unsupervised 프록시가 새 CLI restart를 받아도 자기 트리를 고르는 경우는 #4173 계열과 겹치면서도 더 좁은 별도 버그입니다.왜 지금 중요한가. 배포/업그레이드 직후 가장 흔한 복구 명령이 restart인데, 성공처럼 보이면서 옛 코드가 남는 건 지원·보안 패치 적용을 조용히 실패시킵니다. 워크어라운드(명시
ocx stop후 새 설치에서ocx start/ocx service start)는 본문에 이미 있고 맞습니다. 다만 기본 UX가 그걸 안내하지 않고 성공으로 끝내는 게 문제입니다.라인 -
src/cli/dispatch.tsrestart- 라이브 프록시에 restart를 위임한다. 호출 CLI 버전과 라이브 프록시 버전 skew를 비교하지 않는다.라인 -
src/server/management/system-restart.tsspawnDetachedStart-process.execPath+selfLaunchArgv로 자기 트리를 다시 켠다. 요청 본문에 교체 launcher 힌트가 있어도 현재 구현은 쓰지 않는다.경로/심볼 -
src/lib/self-launch-argv.tsselfLaunchArgv- 설계상 “현재 프로세스 재진입”용이라 unsupervised restart에 그대로 쓰면 업그레이드를 흡수할 수 없다.경로/심볼 -
#4510/#4520desktop restart - 같은 “restart” 단어지만 Codex 데스크톱 축이라 이 이슈를 닫지 않는다.경로/심볼 -
#3464/#2898/#4173- 서비스/업데이트 보장과 인접. 중복 닫기보다 unsupervised 경로를 명시해 연결하는 편이 맞다.메인테이너의 판단이 필요한 지점
/healthz에 설치 경로/entrypoint를 노출해 진단만 먼저 할지너의 추천
bug/cli/service로 두고 업그레이드 신뢰성 버그로 높게 본다. 최소 수정은 skew 감지 후 조용한 self-spawn 성공을 막고 워크어라운드를 문구로 돌려 주는 것, 그다음 호출 CLI launcher를 검증해 교체에 쓰는 것이다. #4173에 링크만 하고 이 이슈는 따로 열린 채로 두라.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 13, 2026
Fixed on
devat72335fc6ac.requestBoundSystemRestartnow computes the version skew after attestation and before the restart POST, atsrc/cli/system-restart-client.ts:131, and returnsrestart_version_skewinstead of handing the restart to the running proxy.src/cli/index.ts:735 turns that into the workaround this issue asked for: stop and start from the installation you are actually running.Placeholder versions are treated as cannot-compare rather than as a mismatch, so
unknownand0.0.0do not break a same-install restart. That case is pinned by a regression test alongside the mismatch case.Landed as #4545, merge commit
e30f1d27eec4ac22baeb6d5212413bc645bb6786, verified by Cross-platform CI run 34775280313 at that exact head. #4545 carries the original work by @Voyagerroc-Lab with aCo-authored-bytrailer; #4529 is closed with the same reference.What this does not do, so nobody reads more into it than landed: there is no automatic upgrade and no launcher replacement. The proxy is not restarted into the new version for you — the point is that a stale restart can no longer report success while an old binary comes back up.
docs(devlog): close the 260914 triage round-lane unit (#4558)
docs(devlog): close the 260914 triage round-lane unit (lidge-jun#4558)