Skip to content

Fix pnpm global-store transitive deps passing silently (#362) - #829

Open
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
mainfrom
agent/fix-pnpm-gvs-transitive-refusal
Open

Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
mainfrom
agent/fix-pnpm-gvs-transitive-refusal

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #362

Root cause

#365 fixed the custom virtualStoreDir half of #362. With pnpm's global virtual store (enableGlobalVirtualStore), node_modules/.modules.yaml records virtualStoreDir: ../../store/v10/links, which is outside the project. The npm crawler deliberately ignores a store outside the importer. So a transitive dependency, which lives only at <store>/v<N>/links/@/<name>/<version>/<hash>/node_modules/<name>, was never found. Since #555 a lockfile-resolved miss counts as a calm "lockfile-only" skip, so agent apply exited 0 with success and Node kept loading the unpatched copy. A direct dependency is found through its node_modules/<dep> link and already got the loud shared-store refusal from #486.

Fix

  • npm_crawler.rs: new reachable_pnpm_global_virtual_store_entries_sync. When .modules.yaml names a real pnpm global virtual store (<store>/v<N>/links with the store's files/ beside it), the scan walk (gather_node_modules) and the apply/rollback resolver (find_by_purls) follow only this project's part of it. They start from the importer's links into the store, then follow each entry's dependency links to sibling entries, scoped ones included, with cycles handled. The shared links dir is never listed, so packages other projects put in the store stay invisible.
  • Every reachable copy is reported at its real store path. The existing shared-store gate in apply/rollback then refuses it (apply_failed, partialFailure, exit 1) with the "set enableGlobalVirtualStore to false and reinstall, or use hosted / vendored mode" remedy. It is no longer passed off as lockfile-only.
  • shared_store.rs: the GVS links dir test is factored into is_pnpm_global_virtual_store_dir, so the crawler and the refusal gate share one definition.
  • docs/ecosystems.md: describes the reachable-entries walk and the transitive refusal.
  • No wrapper changes are needed. npm/, pypi/ and gem/ only dispatch to the binary.

Test evidence

Issue Regression test Without fix With fix
#362 (transitive GVS dep: apply refuses, not lockfile-only success) tests/apply/pnpm_global_virtual_store.rs::transitive_global_virtual_store_dep_is_refused_not_skipped FAIL (exit 0, success) pass
#362 (walk stays scoped to this project) …::other_projects_global_virtual_store_entries_stay_invisible pass pass
#362 (crawler: scan + resolver find transitive and scoped GVS copies, dedup direct dep, skip other projects' entries, ignore non-GVS outside dirs) npm_crawler::tests::test_pnpm_global_virtual_store_reachable_entries_are_walked FAIL pass

Real pnpm check (pnpm 10.28.0, Linux, enableGlobalVirtualStore: true, is-odd@3.0.1 → transitive is-number@6.0.0, hand-staged manifest): apply --offline --json now gives partialFailure, apply_failed "Refusing to patch …/store/v10/links/@/is-number/6.0.0//node_modules/is-number: it is in pnpm's global virtual store …", exit 1, and the store copy is unchanged. On main the same run gives success, package_not_installed "lockfile-only", exit 0.

Local runs:

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • rustfmt: clean on the touched files.
  • cargo test --workspace --all-features: everything passes except 12 tests. All 12 rely on chmod-based write refusals that don't fire as root (this sandbox runs as root, CI doesn't), and none of them touch the crawler.
  • e2e_safety_pnpm: needs patches-api access, which this sandbox doesn't have. CI covers it.

CI on 0c5b583: every workflow is green. Bun compatibility (macOS 1.3.10) first failed on patches-api connect timeouts and passed on a single rerun. Bugbot reviewed 0c5b583 and found no issues.

🤖 Generated with Claude Code


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
With pnpm's enableGlobalVirtualStore, a transitive dependency lives
only in the machine-wide <store>/v<N>/links dir. The npm crawler never
walked that dir, so agent apply treated the package as lockfile-only,
exited 0 with "success", and Node kept loading the unpatched copy.

The crawler now follows this project's links into the global store,
and each entry's dependency links to sibling entries, so every copy the
project loads is found at its real path. Apply then refuses it as
shared, the same way it already refuses a direct dependency, and names
the remedy. Packages other projects put in the store stay invisible.

Fixes #362

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 5, 2026 07:08
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0c5b583. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[burn-down agent] Marked Ready for review at head 0c5b583b340bd368cd676f6e8322ae9f26af00de.

  • CI: all 14 check suites on this head passed (408 check runs, nothing failed; 6 were skipped by path filters). The PR is mergeable and is 0 commits behind main.
  • Bugbot: it reviewed 0c5b583 and found no issues. There are no open review threads.
  • Reviewer focus: reachable_pnpm_global_virtual_store_entries_sync in npm_crawler.rs. It walks only this project's part of the shared <store>/v<N>/links tree, starting from the importer's links and following dependency links. Check that it can never list or reach entries that belong to other projects.

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

2 participants