Client or integration
OpenCodex dashboard
Area
Service lifecycle
Summary
Dashboard and CLI updates stop the working proxy, then npm staging fails with ENOENT. Automatic recovery waits for launchd while holding the runtime mutation lease the new proxy needs, so it times out and falls back to a detached process. Expected: installation succeeds under strict script policy, and failed installs restore one healthy service-managed proxy.
Version
2.63.0-preview.20260923, updating to 2.64.0. The affected source paths are also present in the published 2.64.0 package.
Operating system
macOS 27.0 (26A428), arm64; Node 26.9.0; npm 11.19.1; strict-allow-scripts=true.
Provider and model
Not provider-specific.
Reproduction
- Run OpenCodex as its launchd service.
- Update through the dashboard or
ocx update --tag latest.
- The updater stops the proxy, then its staging install fails:
npm error code ENOENT
npm error syscall lstat
npm error path <prefix>/.ocx-staging-<timestamp>/lib
- Recovery reports no healthy proxy after 21s and falls back to a detached start. The service log repeatedly reports:
error: another process owns the runtime mutation lease at <home>/service-state.json.mutation.lock
A manual global npm install succeeded, but subsequent ocx restart attempts failed while another listener held the configured port. Manual termination and restart restored service.
Logs or error output
ENOENT: no such file or directory, lstat <stage>/lib
Service repaired, but no proxy answered on port 10100 after 21s.
another process owns the runtime mutation lease at <home>/service-state.json.mutation.lock
Findings
Staging: npm's strictAllowScriptsPreflight calls Arborist buildIdealTree before reify creates the global root. The new staging prefix has no lib directory. A disposable local-package reproduction fails with the same ENOENT; creating <stage>/lib makes it pass without disabling strict script policy. Published 2.64.0's transactional-install.mjs still creates only the stage root.
Recovery: bin/ocx.mjs invokes recoverStoppedRuntimeAfterFailure() while holding updateLease; the release is in the following finally. The launchd-started proxy cannot inherit the delegated lease token and blocks acquiring that lease while its parent waits for health. The successful-update path releases before service refresh. The early “replacement refused” recovery path has the same ordering issue.
Expected / suggested fix
- Prepare npm's staging root before strict preflight (POSIX
lib), retaining --allow-scripts=bun and strict policy.
- Make failed-update recovery release the mutation lease before the service health wait, with appropriate ownership/liveness rechecks.
- Restore a single healthy service-managed proxy after a failed install.
A narrowly scoped local npm wrapper creating the staging lib passed a real isolated 2.64.0 install plus verifyInstallTree. This mitigates staging, not the recovery-lock defect. No lock files were removed or protections disabled by this workaround.
Separate from #5750: this worker survives; installation fails and recovery contends with its own lock. Related update architecture: #4173.
Checks
Client or integration
OpenCodex dashboard
Area
Service lifecycle
Summary
Dashboard and CLI updates stop the working proxy, then npm staging fails with ENOENT. Automatic recovery waits for launchd while holding the runtime mutation lease the new proxy needs, so it times out and falls back to a detached process. Expected: installation succeeds under strict script policy, and failed installs restore one healthy service-managed proxy.
Version
2.63.0-preview.20260923, updating to 2.64.0. The affected source paths are also present in the published 2.64.0 package.
Operating system
macOS 27.0 (26A428), arm64; Node 26.9.0; npm 11.19.1; strict-allow-scripts=true.
Provider and model
Not provider-specific.
Reproduction
ocx update --tag latest.A manual global npm install succeeded, but subsequent
ocx restartattempts failed while another listener held the configured port. Manual termination and restart restored service.Logs or error output
Findings
Staging: npm's
strictAllowScriptsPreflightcalls ArboristbuildIdealTreebeforereifycreates the global root. The new staging prefix has nolibdirectory. A disposable local-package reproduction fails with the same ENOENT; creating<stage>/libmakes it pass without disabling strict script policy. Published 2.64.0'stransactional-install.mjsstill creates only the stage root.Recovery:
bin/ocx.mjsinvokesrecoverStoppedRuntimeAfterFailure()while holdingupdateLease; the release is in the followingfinally. The launchd-started proxy cannot inherit the delegated lease token and blocks acquiring that lease while its parent waits for health. The successful-update path releases before service refresh. The early “replacement refused” recovery path has the same ordering issue.Expected / suggested fix
lib), retaining--allow-scripts=bunand strict policy.A narrowly scoped local npm wrapper creating the staging
libpassed a real isolated 2.64.0 install plusverifyInstallTree. This mitigates staging, not the recovery-lock defect. No lock files were removed or protections disabled by this workaround.Separate from #5750: this worker survives; installation fails and recovery contends with its own lock. Related update architecture: #4173.
Checks