Repository navigation
[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
Activity
coderabbitai commented
on Aug 16, 2026 coderabbitaiboton Aug 16, 2026 – with coderabbitaiContributorMore actions🔗 Related PRs
#306 - feat(windows): add tray controls and restart-safety diagnostics [merged]
#533 - fix(update): preserve proxy on npm cache failures [open]
#557 - fix(update): harden npm cache recovery preflight logs [open]
#803 - Codex/pr533 update recovery hardening [open]
#838 - fix(update): recover Windows dashboard proxy after self-update [merged]
#1647 - fix(tray): let the service wrapper exit 0 on an already-live proxy [open]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.- Create Plan
🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:issue_enrichment: auto_enrich: enabled: false
💬 Have feedback or questions? Drop into our discord!
- addedcliCLI, config inject, packaging flagsCLI, config inject, packaging flagsplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)OS/service/tray/ACL (Windows-heavy, not Windows-only)
on Aug 16, 2026 github-actions commented
on Aug 16, 2026 on Aug 16, 2026 – with GitHub ActionsContributorMore actionsMaintainer decision respected
A maintainer has reopened this issue. The automated closure has been deactivated.
Confirmed in current
dev, with one important version distinction.The current updater already stops the managed proxy before package replacement and performs registry-integrity preflight; those protections were added after the reported 2.13.0 build. However, two gaps remain:
- npm can still leave an unusable global package tree on installer failure, and there is no complete transactional restore of both the package directory and npm launchers;
- the current Task Scheduler wrapper still enters the same unconditional five-second loop and does not check whether its baked Bun or CLI entry exists.
I am taking the focused, independently safe wrapper slice first: verify both baked paths before the initial spawn and before every retry, log one actionable
installation incompletefailure, and exit instead of retrying a condition that cannot recover in-process. Normal runtime crashes will retain the existing restart behavior. I will add source-level regressions and document why this is intentionally narrower than a transactional npm rollback.I am not going to improvise a package-directory copy-back in this slice: restoring npm's package tree without the matching global shims and ownership/lock guarantees can make recovery less reliable. The remaining updater rollback boundary should be handled separately with a staged/verified install design.
The focused wrapper fix is now in #1877.
It prevents the exact 1,009-restart failure mode by checking the baked Bun and CLI paths before the first spawn and every retry. A missing path produces one actionable incomplete-install log entry and exits; a proxy that actually starts and later crashes keeps the existing restart behavior.
This is deliberately a partial fix: current
devalready stops the proxy before replacement and performs integrity preflight, but transactional restoration of both an npm package tree and its global launchers still needs a separate staged-update design. #1849 should remain open until that updater boundary is resolved.Split, and staying open. This report contains two defects with different fixes and
different closure conditions, so closing it on the shipped half would have hidden the one
that actually destroyed the install.Half Defect Status A Service wrapper restart-loops against a missing install — the 1,009 × child exited with code 3Fixed by #1877, released in v2.24.0 (verified by git tag --contains 01b212579)B ocx updatedeletes the working install and leaves an unbootable skeleton with no rollbackOpen — now tracked in #1942 #1877 stopped the wrapper from hammering a package that is not there. It does nothing about
how the package came to be missing: the update path delegates tonpm install -g, which
removes the existing install before the replacement is verified, so a mid-flight failure
leaves 107 directories, zero files, and no launcher to run a recovery command with.#1942 carries the transactional shape (stage → verify → snapshot → atomic switch → health
probe → restore on failure), the ordering constraint that makes it work, and the
kill-at-every-boundary matrix it needs.Keeping this issue open as the umbrella so the original evidence — the 107/0 enumeration,
the missing launchers, the 89-minute restart window — stays attached to the story rather
than being split away from it.On the #281 comparison in the report: agreed that the earlier attribution does not fit here.
There was no external process, and a wrapper relaunching a package with nobin/ocx.mjsand
no bundledbun.exeis a different failure from the one #281 closed on.One correction to my comment above: I wrote the
npm install -gsentence in a way that reads
as present-tense causation for the 107-directories/zero-files enumeration. That evidence is
from v2.13.0, and protections have been added since — a prior comment on this issue already
qualified that.The conclusion is unchanged, because the primitive is:
npm install -gis still
destructive-in-place, andsrc/update/has no staging, snapshot, or rollback anywhere on the
path. Verification does exist (checkUpdatePackageIntegritybefore the proxy stops,
isRealBunBinary, and an identity-checked/healthzprobe after restart) — but it is
detect-only and runs after the old install is already gone, so a failed probe has nothing
left to restore. That is the shape #1942 is about.- added a commit that references this issue
on Aug 18, 2026 - added a commit that references this issue
on Aug 18, 2026 Status (260818 campaign sweep): the restart-storm half landed on dev — d09c752 adds missing-install guards to the Windows service wrapper (src/service.ts:1549-1556, exit /b 3 before the loop), so the 1000+-restart storm on a file-less skeleton cannot recur; a3169db scopes the wrapper killer. Still open: post-install verification that the package actually unpacked, rollback of an empty npm install, and a recovery path when launchers are gone (src/update/job.ts still only does a pre-flight registry probe). Kept open for the remaining transactional-update half — being designed together with #1942 in the campaign wp10 follow-up.
- added a commit that references this issue
on Aug 18, 2026 - added a commit that references this issue
on Aug 19, 2026 The remaining half of this issue is fixed on
devby #2079 (1ad131a): the empty-install class is closed by staged verification (a tree that fails the manifest never becomes live), and the restart-loop-on-skeleton class now self-heals — the service wrapper restores the newest.ocx-backup-*sibling before declaring the install incomplete, and the launcher boot probe restores/reaps on the next start. Earlier halves (wrapper restart guard, atomic-replace foundations) were d09c752 and the 030/031 units.- added a commit that references this issue
on Sep 10, 2026 - added 6 commits that reference this issue
on Sep 17, 2026
Summary
On Windows 11, an
ocx updatefrom 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 3events — 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, nosrc/cli/index.ts, and no bundledbun.exeto execute.Environment
opencodex-proxy), wrapperopencodex-service.cmd%APPDATA%\npm\node_modules\@bitkyc08\opencodexEvidence
1. The abandoned install contained directories only
The failed package was preserved before rollback. Recursive enumeration:
95 dependency folders were created under
node_modules(@bufbuild,@hono,@modelcontextprotocol,@napi-rs,@oven,bun, ...). Thenode_modules/bunfolderexists 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.cmdand all sibling launchers were gone. What remained were npm'srename-aside shims, which still pointed at the now-nonexistent target:
A stale scope directory was also left behind (same pattern as #281).
With no
ocxon 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: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.cmdis: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
npm installwhile the service is live and Bun binaries are in use.%APPDATA%\npm;ocxis no longer on PATH.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)
package.json,bin/ocx.mjs,src/cli/index.tsand the bundled runtime exist before declaring success.Service wrapper (separate, arguably higher value)
"installation is incomplete, run X" line and stop rather than looping.
A single existence check would have converted an 89-minute silent outage into one actionable log line.
Notes
(
status=protected,protection=service,rebootSafe=true).