Skip to content

[Bug][Windows] Failed ocx update leaves a file-less package skeleton; service wrapper restart-loops 1,009 times with no integrity check or backoff #1849

Description

@stephen-drew

Summary

On Windows 11, an ocx update from 2.13.0 left the global package directory containing 107 directories and zero files.
Every launcher (ocx, ocx.cmd, ocx.ps1, opencodex, opencodex.cmd, opencodex.ps1) was removed from
%APPDATA%\npm, so no documented recovery command was reachable from a normal shell.

The Task Scheduler service wrapper then restarted the missing entry point every 5 seconds for 89 minutes —
1,009 consecutive child exited with code 3 events — until the package was manually rolled back.

This matches the partial-install behaviour described in #281. That issue was closed with the permanent-crash symptom
attributed to an external monitoring script terminating the restarted proxy. That attribution does not fit this case:
no external process was involved. The wrapper was relaunching a package that had no bin/ocx.mjs, no
src/cli/index.ts, and no bundled bun.exe to execute.

Environment

Item Value
OS Windows 11 Home, 10.0.26100 build 26100, AMD64
Node v24.15.0
npm 11.19.0
opencodex (before/after rollback) 2.13.0
Bundled Bun 1.3.14
Service backend Task Scheduler (opencodex-proxy), wrapper opencodex-service.cmd
Install type npm global, %APPDATA%\npm\node_modules\@bitkyc08\opencodex

Evidence

1. The abandoned install contained directories only

The failed package was preserved before rollback. Recursive enumeration:

TotalDirs   : 107
TotalFiles  : 0

95 dependency folders were created under node_modules (@bufbuild, @hono,
@modelcontextprotocol, @napi-rs, @oven, bun, ...). The node_modules/bun folder
exists and is completely empty. Missing at the package root: package.json,
bin/ocx.mjs, src/cli/index.ts, node_modules/bun/bin/bun.exe.

So npm created the directory tree, then unpacked no file content and did not roll back.

2. Launchers were removed, recovery commands unreachable

%APPDATA%\npm\ocx.cmd and all sibling launchers were gone. What remained were npm's
rename-aside shims, which still pointed at the now-nonexistent target:

.ocx-cl2A1bun           .opencodex-lwm6eEVg
.ocx.cmd-RmK066DM       .opencodex.cmd-XPiSZ201
.ocx.ps1-AKozk9bb       .opencodex.ps1-p35htcAv

A stale scope directory was also left behind (same pattern as #281).

With no ocx on PATH, the documented remedies (ocx service repair, ocx doctor,
ocx update) cannot be run without manually reconstructing the Bun + CLI invocation.

3. Unbounded restart loop with no integrity check

service.log, filtered to this incident:

[16/08/2026 12:33:51.44] child exited with code 3; restarting in 5s
... 1,009 occurrences, one every ~5 seconds ...
[16/08/2026 14:02:50.11] child exited with code 3; restarting in 5s

First failure was 45 seconds after Windows boot (boot 12:33:06), so the broken package was
already on disk before the restart, and the loop began immediately at logon and never self-limited.

For contrast, code 3 appeared only 25 times in the entire preceding log history
(31/07 x1, 02/08 x13, 08/08 x11). This was not pre-existing noise.

The wrapper loop in opencodex-service.cmd is:

:loop
"%OCX_BUN%" "%OCX_CLI%" start --port 10100 >>"%OCX_SERVICE_LOG%" 2>&1
if %ERRORLEVEL% NEQ 0 (
  ping -n 6 127.0.0.1 >nul
  goto loop
)

There is no check that %OCX_BUN% or %OCX_CLI% exist, no failure counter, and no backoff.
A permanently unrecoverable condition is retried identically forever.

Reproduction

  1. Windows, opencodex installed globally via npm and running under the Task Scheduler backend.
  2. Run an update that triggers a global npm install while the service is live and Bun binaries are in use.
  3. npm hits a locked-file error, leaves the package as a directory skeleton, and does not roll back.
  4. Launchers disappear from %APPDATA%\npm; ocx is no longer on PATH.
  5. The service wrapper loops indefinitely on the missing entry point.

Impact

All model routing through the proxy is down. Because the CLI is also gone, the failure is not
self-recoverable through the documented commands. Recovery here required restoring the previous
package directory and launcher shims manually, out of band.

Suggested fixes

Updater (relates to #281)

  • Stage the new version and swap atomically, so a failed unpack cannot replace a working install.
  • On failure, roll back to the previous package and restore launchers rather than leaving a skeleton.
  • Stop the service and release Bun file handles before the install step, not after.
  • Verify package.json, bin/ocx.mjs, src/cli/index.ts and the bundled runtime exist before declaring success.

Service wrapper (separate, arguably higher value)

  • Preflight the launcher and runtime paths before the first spawn; if either is missing, log one clear
    "installation is incomplete, run X" line and stop rather than looping.
  • Add exponential backoff and a consecutive-failure ceiling.
  • Distinguish "crashed at runtime" from "cannot start at all" in the log.

A single existence check would have converted an 89-minute silent outage into one actionable log line.

Notes

  • Rolled back to 2.13.0; the proxy is healthy again and has since survived a restart
    (status=protected, protection=service, rebootSafe=true).
  • The failed install directory has been preserved and can be inspected further if useful.
  • Happy to test a patched updater on this machine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcliCLI, config inject, packaging flagsplatformOS/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