Skip to content

[Bug][Windows]: dashboard update aborts after proxy stops when history restore exits non-zero #3008

Closed

Description

@SeanChengN

Client or integration

OpenCodex dashboard

Area

Installation or packaging

Summary

A dashboard npm update can abort after ocx stop has already stopped the proxy if resume-history restoration returns a non-zero status.

I hit this while updating 2.34.0 to 2.36.0 on Windows. The update job stopped the service, left no listener on the configured port, and cleared the runtime process state, but package replacement never ran. A codex-history-backup-*.json manifest was still present because history restoration had not completed.

The dashboard reported update command failed (1). The proxy was down and the installed package was still 2.34.0.

Expected: proxy shutdown and history cleanup should be evaluated separately. If the proxy is confirmed stopped but history restoration is incomplete, the update should continue with a warning and leave the manifest for a later retry.

Reproduction

  1. Install OpenCodex through npm and run it with the Windows Task Scheduler service and tray.
  2. Have a pending codex-history-backup-*.json manifest, for example when the Codex app keeps the history database busy during restore.
  3. Start an update from the dashboard with restart enabled.
  4. The worker runs ocx stop. The proxy and service stop, but history restoration returns a non-zero status.
  5. The update aborts before package replacement. The configured port has no listener, no runtime PID remains, and the old package version is still installed.

State from the failed job:

Update job queued for 2.34.0 -> 2.36.0.
Verified @bitkyc08/opencodex@2.36.0 integrity metadata ...
exit 1 · 476 bytes of output withheld
error: update command failed (1)

As a control, I installed 2.36.0 into a separate npm prefix with the same package and Bun lifecycle-script allowance. The staging install completed and the bundled Bun binary passed the package checks, so this was not a registry, cache, or package-integrity failure.

The current control flow also appears to make the later warning unreachable in this case:

if (stop.status !== 0 || readPid() || readRuntimePort()) {
  // aborts here
}
if (historyRestoreIncomplete()) {
  // not reached when the restore caused the non-zero exit
}

A narrow fix would keep aborting when PID/runtime state remains or when ocx stop fails for another reason, but allow the known incomplete-history case to continue once the proxy is confirmed stopped.

Related but not duplicate: #1191 covered incorrect history-error classification. This report is about the updater treating a cleanup/restore exit code as proof that the proxy is still running.

Version

2.34.0 -> 2.36.0. The same condition is still present in the current main branch.

Operating system

Windows 10 22H2, build 19045, x64

Provider and model

Not provider-specific

Logs or error output

update command failed (1)

The dashboard job intentionally withholds the child output, so I checked the liveness state separately after the failure.

Screenshots and supporting files

Current source: https://github.com/lidge-jun/opencodex/blob/main/src/update/index.ts#L229-L254

Redacted configuration

{
  "installer": "npm",
  "service": "Windows Task Scheduler",
  "tray": true,
  "port": 11000,
  "restart": true,
  "channel": "latest"
}

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
    guiDashboard, tray, settings UI
    installInstallation or packaging
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Aug 30, 2026
  2. lidge-jun commented on Aug 30, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 71 / 80

    이 이슈는 윈도우에서 대시보드 업데이트가 패키지를 갈아끼우기 전에 멈추는 버그다. 작성자는 2.34.0에서 2.36.0으로 올리던 중, 업데이트가 ocx stop 뒤에 끝장났다고 한다. 프록시는 이미 꺼져 있고 포트도 비어 있고 pid도 없는데, 설치본은 2.34.0 그대로였다. 원인은 Codex 이어받기 히스토리를 되돌리다가 종료 코드 1이 나온 것이다. 히스토리 백업 매니페스트가 남아 있었다. 지금 dev HEAD는 df8b388 (#3007) 이고, 작성자가 가리킨 가드는 그대로 있다. 미리보기 배포는 계획에 없다. types.ts/config.ts 분할과도 안 겹친다.

    업데이트 코드는 src/update/index.ts 다. 패키지 파일을 바꾸기 전에 살아 있는 프록시를 끈다. 서비스가 있거나 pid가 있거나 runtime-port 기록이 있으면 ocx stop 을 자식 프로세스로 돌린다. 그 다음 259행에서 세 가지를 한 번에 본다. stop.status 가 0이 아니거나 pid가 남았거나 runtime-port가 남았으면 프록시를 못 껐다고 보고 끝낸다. 그 뒤에 있는 269행 경고는, 히스토리 되돌리기가 덜 끝났을 때 경고만 하고 업데이트를 계속하라는 글이다. 그런데 그 경고는 259행 가드를 통과해야만 나온다.

    ocx stop 쪽은 src/cli/index.ts handleStop 이다. 여기서 프록시를 먼저 죽이고 pid와 runtime-port를 지운다. 그 다음에 restoreSharedClientStateAfterStop 가 네이티브 Codex 설정과 히스토리를 되돌린다. 히스토리 작업이 실패하면 restoreNativeCodexAsync 의 success 가 거짓이 되고, 742행에서 stopFailed 가 참이 되며, 746행에서 process.exitCode 가 1이 된다. 프록시는 이미 죽었는데 종료 코드만 1이다. 업데이트 가드는 그 1을 아직 산다고 읽는다.

    고치는 폭은 좁다. pid나 runtime-port가 남아 있으면 지금처럼 막아야 한다. 프록시가 확인되게 꺼졌고, 남은 증거가 히스토리 매니페스트뿐이면 269행 경고를 찍고 패키지 교체로 가면 된다. 매니페스트는 나중에 ocx doctor 와 ocx stop 으로 다시 시도하면 된다. 작성자가 말한 기대와 같다.

    작성자가 적어 준 흐름과 HEAD가 같다.

    히스토리 되돌리기 실패로 stop이 1을 내면, pid와 포트가 비어 있어도 패키지 교체는 시작되지 않는다. 이슈 1191은 히스토리 오류 분류 이슈라 중복이 아니다.

    라인 259-267 (src/update/index.ts) - stop.status 가 0이 아닌 경우를 pid/runtime-port와 한 묶음으로 본다.

    라인 269-275 (src/update/index.ts) - 히스토리 경고가 그 가드 뒤에 있어 이번 경로에서는 안 나온다.

    라인 742-746 (src/cli/index.ts handleStop) - 히스토리 되돌리기 실패를 stop 종료 코드 1로 올린다.

    라인 20-28 (src/update/index.ts historyRestoreIncomplete) - 백업 json이 있으면 덜 끝났다고 보는 판정은 맞다.

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

    • ocx stop 의 종료 코드를 히스토리 실패와 프록시 생존으로 나눌지, 아니면 업데이트 가드만 느슨하게 할지
    • 히스토리 매니페스트가 남은 채 패키지를 갈아끼우는 것을 이번 범위에서 허용할지. 작성자 기대는 허용이다
    • 이슈 1191과 이 이슈를 묶지 말지. 분류 버그와 업데이트 중단은 다른 코드다

    너의 추천
    좁은 고침 PR을 받으세요. src/update/index.ts 259행 가드를 쪼개서, pid나 runtime-port가 남아 있을 때만 중단하세요. 프록시가 꺼진 뒤 히스토리 매니페스트만 남으면 269행 경고를 찍고 패키지 교체로 가세요. 테스트는 stop 종료 코드 1에 pid/port 없고 매니페스트가 있으면 교체까지 가는지, pid가 남아 있으면 여전히 멈추는지를 고정하면 됩니다. types/config 분할로 닫을 이슈가 아니고 중복도 아닙니다.

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

  3. lidge-jun commented on Aug 31, 2026

    @lidge-jun
    Owner

    Fixed on dev in 330470e (PR #3118).

    handleStop now distinguishes a history-only failure from a real stop failure. A restore
    where only Codex history metadata could not be finalized exits 79 — the proxy is down,
    the service is stopped, and a backup manifest is waiting for review, so ocx update may
    proceed and prints the manifest warning. Config or catalog failing is still an ordinary
    failure and still aborts the update, because a client reads those. Both updater lanes
    (src/update/index.ts and bin/ocx.mjs) share one decidePostStopUpdate, which is what
    keeps them from disagreeing about the same situation the way this shipped originally.

    Reviewing that path turned up the same defect in fifteen other places, all fixed in the
    same PR. The shape is always: something cannot be determined, the code treats "could not
    determine" as "fine", and shared client config comes down while a proxy may still be
    serving — leaving Codex or Grok pointed at a process that is gone.

    The ones most likely to have bitten you:

    • POST /api/stop consumed a boolean that collapsed "no service installed" with "the
      manager refused to stop", so a direct API stop could tear down shared config while a
      manager was still there to respawn the proxy.
    • ocx restore returned success on the Codex-already-native path and on --json without
      ever removing the Grok fence, so it reported a complete teardown with half of it undone.
    • ocx uninstall read an unreadable service state as "not installed" and a missing pid
      file as "no proxy running", then restored shared config and reported success.

    ocx stop also defers shared teardown to itself now, backed by a receipt on disk, because
    a stopped Windows Task Scheduler can respawn the proxy through its :loop wrapper and the
    proxy itself cannot verify its own post-exit restart window. On that backend the dashboard
    Stop button refuses with respawnable_service and asks you to run ocx stop, rather than
    half-performing a stop it cannot verify — documented in the dashboard and management API
    references across all locales.

    Full round-by-round record: devlog/_plan/260831_prio70_train_round2/051_wp5_outcome.md.

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 workingguiDashboard, tray, settings UIinstallInstallation or packagingplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions