Skip to content

[Bug][Windows] Bare npm reinstall can leave a live proxy on a mixed old/new module graph #2459

Closed

Description

@twk0672005

Client or integration

Codex App

Area

Service lifecycle

Summary

On Windows, a bare global npm reinstall performed while an existing OpenCodex Bun proxy is still serving can leave one process with an old in-memory module graph while later dynamic imports resolve against the newly replaced package tree. The proxy remains bound to port 10100 and /healthz can still answer, but every POST /v1/responses fails with a named-export error until the old process is fully terminated and the service is restarted.

ocx update is not implicated: v2.31.0 already stops a live proxy before replacement specifically to prevent mixed old/new code. This report is limited to a bare npm install -g reinstall/fallback performed while a service-managed proxy remains live, followed by ocx service repair accepting that stale process as healthy.

This is different from #1849: the replacement package tree was complete and valid. The failure was a live-process / new-package split, not a missing-file skeleton. It is also distinct from #764's stop/wrapper resurrection and the auto-update health behavior discussed in #281.

Reproduction

Observed sequence; not yet reduced to a clean two-version standalone reproducer. The pre-update package version was not captured.

  1. Start the Windows Task Scheduler service/proxy on port 10100 with an older global npm installation.
  2. While that Bun process is still alive, run the immutable replacement command npm install -g @bitkyc08/opencodex@2.31.0.
  3. The existing Bun PID remains alive after the package files are replaced.
  4. Send a normal Codex App request. In the observed session this reached the Windows collaboration/process-helper import path; no standalone HTTP-only reproducer has yet been run.
  5. The request fails repeatedly with a named-export error, although the named export exists in the new on-disk file.
  6. Fully stop the old Bun/VBS/CMD process chain and start the service again. Requests recover immediately.

Evidence from the observed run:

  • Live Bun process creation: approximately 10 minutes before the npm package/source mtimes.
  • Live process parent no longer existed; the runtime-port journal referenced a different stale PID.
  • Installed package after replacement: 2.31.0.
  • The current on-disk src/lib/windows-elevation.ts contained resolveTrustedWindowsTaskkillExe, while the live process reported that export as missing.
  • The error repeated for every /v1/responses request until process replacement.

Expected behavior:

  • A bare npm reinstall followed by ocx service repair should not allow a live proxy to serve from a mixed module graph; or
  • repair should detect a proxy whose start time/package identity predates the installed package and replace it before reporting health; or
  • the npm fallback documentation/output should require a verified service stop before replacement.

Actual behavior:

  • Port 10100 remained occupied by the pre-update Bun process.
  • Health/port presence did not prove that request handling was valid.
  • Requests failed until the entire old process chain was replaced.

Version

Pre-update version: not captured. Final installed version at failure time: @bitkyc08/opencodex@2.31.0.

Operating system

Windows 11 Home, build 10.0.26200.

Provider and model

Not provider-specific. The failure occurred before provider translation completed.

Logs or error output

SyntaxError: Export named 'resolveTrustedWindowsTaskkillExe' not found in module 'C:\Users\<USER>\AppData\Roaming\npm\node_modules\@bitkyc08\opencodex\src\lib\windows-elevation.ts'.
POST - http://127.0.0.1:10100/v1/responses failed

The same pair repeated for each attempted response. The new on-disk file did contain the export, which is why this points to an old in-memory module graph rather than a missing export in 2.31.0.

Screenshots and supporting files

No private screenshots attached. Process timestamps, task state, package mtimes, listener ownership, and redacted service-log lines were captured during diagnosis.

Related issues: #1849 (incomplete package replacement), #764 (Windows service stop/wrapper resurrection), and #281 (update/health behavior). This report is specifically the complete-new-package + still-live-old-process split on the bare npm reinstall path.

Redacted configuration

{
  "service_backend": "scheduler",
  "listen_port": 10100,
  "install_method": "npm -g",
  "installed_version_after_replacement": "2.31.0",
  "secrets_removed": true
}

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
    installInstallation or packaging
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Aug 23, 2026
  2. lidge-jun commented on Aug 24, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 63 / 80

    설명: 이 이슈는 윈도우에서 전역 엔피엠으로 패키지를 다시 깔 때, 이미 떠 있는 번 프록시를 안 멈추면 한 프로세스가 예전 모듈 지도를 들고 새 파일을 읽게 된다고 한다. 포트 10100 은 그대로 잡혀 있고 헬스 주소는 대답한다. 그런데 응답 길 POST /v1/responses 는 이름 보낸 오류로 죽는다. 예전 프로세스를 완전히 죽이고 서비스를 다시 켜야 요청이 살아난다. 작성자는 업데이트 명령이 아니라 맨손 엔피엠 다시 깔기라고 못 박았다. 업데이트 2.31.0 은 이미 산 프록시를 먼저 멈춘다. 이 보고는 서비스가 관리하는 프록시가 살아 있는 채로 패키지만 바뀐 길이다. 그 다음 서비스 수리가 그 낡은 프로세스를 건강하다고 받는다. 지금 CURRENT dev HEAD 는 35a89903c 이다. 이번 시간에 origin/dev 는 그대로다. 새 머지는 없다. package.json 은 2.27.0 이다. src/config.ts 는 3238줄이다. src/runtime 폴더는 지금 HEAD 에 없다.

    살아 있는 재현은 이렇다. 윈도우 작업 스케줄러 서비스가 예전 전역 설치로 포트 10100 에서 프록시를 켠다. 그 번 프로세스가 살아있는 동안 새 패키지 2.31.0 을 전역으로 덮어 쓴다. 기존 번 피아이는 파일이 바뀐 뒤에도 살아 있다. 코덱스 앱 요청이 윈도우 권한 올리기 가져오기 길로 간다. 디스크의 새 파일에는 resolveTrustedWindowsTaskkillExe 가 있다. 그런데 산 프로세스는 그 보낸을 없다고 한다. 같은 쌍이 응답 길마다 반복된다. 부모 프로세스도 이미 없고, 런타임 포트 기록의 피아이디도 다른 낡은 값이다. 프로세스 시작 시각이 패키지 파일 시각보다 대략 십 분 빠르다. 기대는 맨손 다시 깔기 뒤에 서비스 수리가 낡은 모듈 지도를 건강하다고 보지 않는 것이다. 또는 수리가 시작 시각과 패키지 신원을 비교해서, 설치보다 오래된 프록시를 건강 보고 전에 바꾸는 것이다.

    지금 HEAD 를 열었다. src/lib/windows-elevation.ts 215줄에 resolveTrustedWindowsTaskkillExe 가 있다. 새 패키지 파일에는 그 이름이 맞다. 작성자가 본 오류는 파일이 비어서가 아니다. 산 프로세스가 예전 지도를 들고 새 파일을 읽어서다. 1849 는 교체가 덜 된 뼈대다. 이번은 새 패키지가 완전한 뒤에도 예전 프로세스가 남는 갈라짐이다. 764 는 윈도우 서비스 중지와 래퍼가 다시 살아나는 길이다. 281 은 업데이트와 헬스다. 셋 다 닫지 말 것. 2412 는 패키지 관리자가 심과 백업을 같이 지우는 다른 구멍이다. 이번은 심이 아니라 살아 있는 프록시 모듈 지도다. 2412 와 한 기차에 섞지 말 것.

    서비스 쪽을 보면 구멍은 헬스 기준이다. src/server/proxy-liveness.ts 222줄은 헬스 주소가 프로세스만 대답하면 살아 있다고 본다. 레디니스는 코덱스 동기만 더 본다. 둘 다 패키지 신원과 프로세스 시작 시각을 비교하지 않는다. src/service.ts 3239줄 상태 보고는 프록시가 포트에서 대답하는지만 본다. 3147줄 낡은 표시는 구운 경로와 자산이 없을 때다. 패키지가 바뀌었는데 프로세스가 예전인 경우는 낡은 것이 아니다. 그래서 수리가 그 프로세스를 건강하다고 받는다. 업데이트 일은 이미 산 프록시를 멈춘다. 그 길을 넓히지 말 것. 고침은 맨손 다시 깔기 길과 수리의 건강 기준만 좁게 둔다. 수리가 프로세스 시작이 패키지보다 오래면 교체하게 한다. 헬스만으로 응답 길이 살아 있다고 말하지 말 것.

    사용자 길이로는 다시 깐 뒤에 코덱스 앱 요청이 전부 이름 보낸 오류로 죽어서 63. 헬스가 초록이라 수리가 조용히 넘어간다. 카탈로그 팁은 Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. Cursor 정적 카탈로그는 opus-4-8-fast / opus-5-fast. 2334 CursorCredentialRouter 는 여전히 src/providers/cursor-pool.ts 모듈+테스트만 있고 어댑터에 연결되지 않았다. 2332 H2 는 discovery 전용. 2320 overflow + 2342 는 이미 dev. 2188 사이드카는 이미 dev. 2361 __omit__ 는 이미 HEAD 다. combo-stream-preflight.ts 는 src/server/responses 에 있다. src/lib/bounded-body.ts 128줄은 이미 죽은 시그널에서 취소 없이 던진다. 2426 코덱스 웹소켓 나가는 크기 가드는 없다. 2451 wait 허용 목록은 아직 하이픈이다. 이번 이슈는 그 구멍들이 아니다.

    src/lib/windows-elevation.ts 라인 215 - 새 파일에는 작성자가 못 찾았다는 보낸이 있다. 산 프로세스 지도가 예전이다
    src/server/proxy-liveness.ts 라인 222 - 헬스는 프로세스가 대답만 하면 살아 있다고 본다. 패키지 신원은 안 본다
    src/service.ts 라인 3147 - 낡은 표시는 구운 경로와 자산이다. 패키지보다 오래된 프로세스는 여기 없다
    src/service.ts 라인 3239 - 상태 보고는 포트 대답만 본다. 응답 길이 이름 보낸으로 죽어도 초록일 수 있다
    이슈 2412 - 심과 백업이 같이 사라지는 다른 구멍이다. 이번 모듈 지도와 섞지 말 것
    이슈 1849 / 764 / 281 - 덜 된 교체, 래퍼 부활, 업데이트 헬스다. 닫지 말 것

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

    • 수리가 프로세스 시작과 패키지 시각을 비교할지. 비교하는 편이 맞다. 헬스만으로 충분하다고 하지 말 것
    • 맨손 엔피엠 다시 깔기 안내가 서비스 중지를 요구할지. 업데이트 길은 이미 멈춘다. 문서만 고치면 수리가 낡은 프로세스를 계속 받을 수 있다
    • 2412 와 한 기차에 섞지 말 것. 심 파괴와 모듈 지도는 다르다
    • 1849 764 281 을 닫지 말 것. 이번은 완전한 새 패키지 위의 산 프로세스다
    • 업데이트 멈춤 길을 넓히지 말 것. 맨손 길과 수리 기준만 좁게 둔다

    너의 추천
    이슈는 열어 둔다. 수리가 패키지보다 오래된 프록시를 건강하다고 보지 않게 한다. 헬스만으로 응답 길을 통과라고 하지 말 것. 업데이트 멈춤 길은 그대로 둔다. 2412 와 1849 와 764 와 281 은 닫지 않는다. 호출 길을 넓히지 말 것. 라벨은 그대로 둔다. 프리뷰 배포가 아니다.

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

  3. lidge-jun commented on Aug 25, 2026

    @lidge-jun
    Owner

    Fixed on dev by #2632 (squash 303cc3c8c).

    Your framing is what made this actionable, particularly the part that is easy to skip past: /healthz kept answering 200 while every POST /v1/responses failed. That is the worst possible shape — every supervisor sees a healthy process while the proxy is unusable. The named-export failure is the symptom; the lying health check is what makes it hard to notice.

    package.json filesystem identity (device, inode, ctime, size) is captured at boot and compared per request. On replacement or unreadability, /healthz and /readyz return 503 restart_required, and /v1/* returns a typed package_tree_changed error naming the cause and the remedy. Management routes stay reachable so you can restart and diagnose through the proxy that just refused you.

    Identity rather than version, deliberately: reinstalling the same version replaces the file while the version string does not move, and that case has its own test.

    Your point that ocx update is not implicated held up — it already stops the proxy before replacement. This is specifically about a replacement OpenCodex did not perform, which is why the answer is honest detection rather than coordination.

    One review addition worth naming: the check is throttled to one filesystem stat per second on the ok path, because it runs on every /v1/* request and would otherwise add a syscall to the hot path forever to catch an event that happens once per install. A failing reading is never cached, so a repaired install recovers on its own.

    Not verified on Windows — no Windows host here. The guard is platform-independent and is proven by filesystem and HTTP fault injection rather than by reproducing the npm reinstall.

  4. twk0672005 commented on Aug 26, 2026

    @twk0672005
    Author

    Thanks for the detailed fix and for preserving the distinction from ocx update. The package-identity approach looks solid.

    I noticed two post-merge review findings are still unresolved:

    1. An already-established /v1/responses WebSocket does not appear to recheck package integrity for each response.create turn: fix(server): refuse requests after the package tree is replaced under a live proxy (#2459) #2632 (comment)
    2. The degraded /healthz response may omit the restart capability/attestation needed by the recommended ocx restart recovery path: fix(server): refuse requests after the package tree is replaced under a live proxy (#2459) #2632 (comment)

    Would you prefer tracking these as follow-up issues, or addressing them before the next release?

    I can also verify the final fix on the original Windows environment once a containing release or testable build is available.

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 workinginstallInstallation or packagingplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)serviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions