Skip to content

[Bug][macOS]: strict npm staging fails with ENOENT; recovery blocks on its own mutation lease #5760

Description

@robin-liquidium

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

  1. Run OpenCodex as its launchd service.
  2. Update through the dashboard or ocx update --tag latest.
  3. 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
    
  4. 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

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

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)priority: P1High: reproducible failure in a core path (routing, failover, account pool, streaming, usage, auth,serviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions