aiox update fails on pnpm/yarn projects — runs npm install in project root
Summary
aiox update crashes on any project that uses pnpm (or yarn) as its package manager. The updater unconditionally shells out to npm install <pkg> --save-exact in the project root (process.cwd()). When the project's node_modules was created by pnpm, npm's @npmcli/arborist cannot read the dependency tree and throws, so the whole update aborts (then rolls back).
The update never gets to the part that actually matters — copying the framework files — because it dies on the package-install step.
Environment
|
|
aiox-core / @aiox-squads/core |
5.2.9 (also reproduced on 5.0.7) |
| Node |
v24.13.0 |
| npm |
v11.12.1 |
| Project package manager |
[email protected] (declared via "packageManager" in package.json) |
| OS |
macOS (Darwin 25.5.0) |
Steps to reproduce
- In any pnpm-managed project that has AIOX installed (
.aiox-core/ present), where pnpm-lock.yaml and a pnpm-built node_modules/.pnpm/ exist.
- Run
aiox update.
Expected
Framework files updated from the new @aiox-squads/core version; project config preserved.
Actual
🔄 AIOX Update
✗ Update failed
Error: Command failed: npm install @aiox-squads/[email protected] --save-exact
npm error Cannot read properties of null (reading 'matches')
npm error A complete log of this run can be found in: ~/.npm/_logs/...-debug-0.log
Rolled back to previous version.
npm debug log (key frames):
... silly placeDep ROOT @babel/[email protected] OK for: [email protected] want: ^7.12.1
... verbose stack TypeError: Cannot read properties of null (reading 'matches')
... verbose stack at Link.matches (.../@npmcli/arborist/lib/node.js:1183:41)
... verbose stack at Link.canDedupe (.../@npmcli/arborist/lib/node.js:1127:15)
... verbose stack at PlaceDep.pruneDedupable (.../@npmcli/arborist/lib/place-dep.js:426:14)
...
... silly unfinished npm timer idealTree:node_modules/.pnpm/[email protected]/node_modules/cac
... verbose cwd /path/to/project
The .pnpm/... path and the unrelated project devDeps (@babel/*, jest, ts-node, typedoc) in the resolution confirm npm is trying to reconcile a pnpm-built tree in the project root and failing.
Root cause
packages/installer/src/updater/index.js, AIOXUpdater.applyUpdate():
const npmOptions = {
cwd: this.projectRoot, // <-- project root, may be pnpm/yarn-managed
stdio: this.options.verbose ? 'inherit' : 'pipe',
timeout: 120000,
};
if (previousCorePackage && previousCorePackage.packageName !== CORE_PACKAGE_NAME) {
execFileSync('npm', ['uninstall', previousCorePackage.packageName], npmOptions);
}
const packageSpecifier = `${CORE_PACKAGE_NAME}@${targetVersion}`;
execFileSync('npm', ['install', packageSpecifier, '--save-exact'], npmOptions); // <-- crashes on pnpm trees
The install is hardcoded to npm, in the project root, regardless of the project's package manager. Everything after this line (getPackageRoot → generateUpgradeReport → applyUpgrade file copy) is correct and package-manager-agnostic — it's only the install step that breaks.
Suggested fixes (any one resolves it)
-
Detect the package manager (from packageManager field, or presence of pnpm-lock.yaml / yarn.lock) and dispatch the equivalent command (pnpm add -E <pkg>, yarn add -E <pkg>).
-
Don't install into the project root at all. The updater only needs the package contents to copy .aiox-core/ from. Resolve/download the tarball to a temp dir (or reuse a globally installed @aiox-squads/core) and point getPackageRoot / applyUpgrade at that staged source. This avoids touching the project's node_modules and lockfile entirely — which is cleaner anyway, since most projects don't want @aiox-squads/core as a project dependency.
-
At minimum: add --no-package-lock --no-save style isolation and fall back gracefully (or emit a clear error telling the user to update manually) when the install step fails, instead of only saying "Cannot read properties of null".
Workaround (for affected users)
Update the global CLI, then overlay the package .aiox-core/ manually, preserving project config:
# 1. Update global CLI (run from $HOME, not the project)
npm install -g aiox-core@latest
# 2. Stage the new package source
mkdir -p ~/.aiox-staging && cd ~/.aiox-staging
npm pack @aiox-squads/core@<version> && tar xzf aiox-squads-core-*.tgz
# 3. Back up, then overlay package files over .aiox-core/, then restore your config
cp -a /path/to/project/.aiox-core ~/.aiox-core.bak
cp -a package/.aiox-core/. /path/to/project/.aiox-core/
# restore core-config.yaml / pro-config.yaml / feature-registry.yaml / version.json from the backup
# 4. Install the .aiox-core isolated deps with npm (its own node_modules + package-lock.json — safe)
cd /path/to/project/.aiox-core && npm install --omit=dev
# 5. Verify
cd /path/to/project && aiox validate && aiox doctor
Note: .aiox-core/ has its own package-lock.json and isolated node_modules, so npm install inside .aiox-core is safe — it's only npm install in the project root (pnpm-managed) that breaks.
Impact
Blocks aiox update for every pnpm/yarn user. pnpm is common in monorepos, which are exactly the kind of projects likely to adopt AIOX.
aiox updatefails on pnpm/yarn projects — runsnpm installin project rootSummary
aiox updatecrashes on any project that uses pnpm (or yarn) as its package manager. The updater unconditionally shells out tonpm install <pkg> --save-exactin the project root (process.cwd()). When the project'snode_moduleswas created by pnpm, npm's@npmcli/arboristcannot read the dependency tree and throws, so the whole update aborts (then rolls back).The update never gets to the part that actually matters — copying the framework files — because it dies on the package-install step.
Environment
aiox-core/@aiox-squads/core[email protected](declared via"packageManager"inpackage.json)Steps to reproduce
.aiox-core/present), wherepnpm-lock.yamland a pnpm-builtnode_modules/.pnpm/exist.aiox update.Expected
Framework files updated from the new
@aiox-squads/coreversion; project config preserved.Actual
npm debug log (key frames):
The
.pnpm/...path and the unrelated project devDeps (@babel/*,jest,ts-node,typedoc) in the resolution confirm npm is trying to reconcile a pnpm-built tree in the project root and failing.Root cause
packages/installer/src/updater/index.js,AIOXUpdater.applyUpdate():The install is hardcoded to
npm, in the project root, regardless of the project's package manager. Everything after this line (getPackageRoot→generateUpgradeReport→applyUpgradefile copy) is correct and package-manager-agnostic — it's only the install step that breaks.Suggested fixes (any one resolves it)
Detect the package manager (from
packageManagerfield, or presence ofpnpm-lock.yaml/yarn.lock) and dispatch the equivalent command (pnpm add -E <pkg>,yarn add -E <pkg>).Don't install into the project root at all. The updater only needs the package contents to copy
.aiox-core/from. Resolve/download the tarball to a temp dir (or reuse a globally installed@aiox-squads/core) and pointgetPackageRoot/applyUpgradeat that staged source. This avoids touching the project'snode_modulesand lockfile entirely — which is cleaner anyway, since most projects don't want@aiox-squads/coreas a project dependency.At minimum: add
--no-package-lock --no-savestyle isolation and fall back gracefully (or emit a clear error telling the user to update manually) when the install step fails, instead of only saying "Cannot read properties of null".Workaround (for affected users)
Update the global CLI, then overlay the package
.aiox-core/manually, preserving project config:Note:
.aiox-core/has its ownpackage-lock.jsonand isolatednode_modules, sonpm installinside.aiox-coreis safe — it's onlynpm installin the project root (pnpm-managed) that breaks.Impact
Blocks
aiox updatefor every pnpm/yarn user. pnpm is common in monorepos, which are exactly the kind of projects likely to adopt AIOX.