Is there an existing issue for this?
This issue exists in the latest npm version
While trying to fix #5463 via #9330 I found this issue
Current Behavior
When a workspace's directory has been deleted from disk and the entry has been removed from the root package.json's workspaces array, running npm install --install-strategy=linked re-creates the deleted workspace directory as an empty shell containing a self-referential symlink:
packages/b/
└── node_modules/
└── b -> ..
No source files (no package.json, no index.js) are restored — just the directory and a <workspace>/node_modules/<name> -> .. symlink loop. The symlink target (..) points at the empty packages/b/ directory itself, so resolving it leads back to the same empty shell.
The hoisted (default) strategy does not exhibit this — the deleted directory stays deleted.
Expected Behavior
A workspace removed from package.json's workspaces array and deleted from disk should remain absent after npm install, regardless of install-strategy. The linked strategy should match the hoisted strategy's behavior here.
Steps To Reproduce
PROJ=/Users/me/tmp-cli-test # any non-/tmp path; /tmp is /private/tmp via symlink and confuses npm
rm -rf "$PROJ" && mkdir -p "$PROJ/packages/a" "$PROJ/packages/b" && cd "$PROJ"
cat > .npmrc << 'EOF'
install-strategy=linked
EOF
cat > package.json << 'EOF'
{ "name": "host", "version": "1.0.0", "workspaces": ["packages/a", "packages/b"] }
EOF
echo '{ "name": "a", "version": "1.0.0" }' > packages/a/package.json
echo '{ "name": "b", "version": "1.0.0" }' > packages/b/package.json
npm install >/dev/null
# Remove workspace b: drop the directory AND the entry in package.json
rm -rf packages/b
node -e "const p=require('./package.json');p.workspaces=['packages/a'];require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2))"
test -d packages/b && echo "before reinstall: present" || echo "before reinstall: gone"
npm install >/dev/null
test -d packages/b && echo "after reinstall: present" || echo "after reinstall: gone"
# Inspect what got recreated
find packages/b
readlink packages/b/node_modules/b
Observed
before reinstall: gone
after reinstall: present
packages/b
packages/b/node_modules
packages/b/node_modules/b
.. # ← packages/b/node_modules/b -> ..
The recreated directory contains only a self-loop symlink. There is no packages/b/package.json and no source files.
Comparison: hoisted strategy
Repeating the same steps without install-strategy=linked (or with install-strategy=hoisted) yields:
before reinstall: gone
after reinstall: gone
Environment
- npm: built from
npm/cli branch fix/prune-removed-workspace-from-package-lock at commit ea023dd0f
- Node.js: v24.15.0
- OS: macOS (Darwin 25.4.0)
- npm config:
Is there an existing issue for this?
This issue exists in the latest npm version
While trying to fix #5463 via #9330 I found this issue
Current Behavior
When a workspace's directory has been deleted from disk and the entry has been removed from the root
package.json'sworkspacesarray, runningnpm install --install-strategy=linkedre-creates the deleted workspace directory as an empty shell containing a self-referential symlink:No source files (no
package.json, noindex.js) are restored — just the directory and a<workspace>/node_modules/<name> -> ..symlink loop. The symlink target (..) points at the emptypackages/b/directory itself, so resolving it leads back to the same empty shell.The hoisted (default) strategy does not exhibit this — the deleted directory stays deleted.
Expected Behavior
A workspace removed from
package.json'sworkspacesarray and deleted from disk should remain absent afternpm install, regardless ofinstall-strategy. The linked strategy should match the hoisted strategy's behavior here.Steps To Reproduce
Observed
The recreated directory contains only a self-loop symlink. There is no
packages/b/package.jsonand no source files.Comparison: hoisted strategy
Repeating the same steps without
install-strategy=linked(or withinstall-strategy=hoisted) yields:Environment
npm/clibranchfix/prune-removed-workspace-from-package-lockat commitea023dd0finstall-strategy=linked