Skip to content

Fix pnpm modulesDir store being skipped (#661, #696) - #698

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-pnpm-modules-dir-crawl
Oct 5, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-pnpm-modules-dir-crawl

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #661
Fixes #696

Summary

pnpm projects that set modulesDir (pnpm 10.12+) are now patched and attested correctly:

Root cause

pnpm's modulesDir (modulesDir: in pnpm-workspace.yaml, modules-dir= in .npmrc) renames node_modules. From pnpm 10.12 the virtual store moves with it. The npm crawler only collects directories literally named node_modules, plus the roots in configured_install_roots (yarn --modules-folder, Rush). It never read modulesDir. The miss surfaced as the lockfile-only "not installed" skip in agent mode (#661), and the hosted lockfile_basis exemption turned it into an attestation in vex (#696). The same exemption also excused every other crawler blind spot, such as pnpm stores outside the project.

Changes

  • crawlers/npm_crawler.rs
    • configured_install_roots now includes pnpm's modules dirs:
      • the configured modulesDir: nearest pnpm-workspace.yaml first, else the nearest .npmrc modules-dir. It is resolved per project, like pnpm does, and honored only strictly inside the project (same guard as yarn's modules folder).
      • any direct child dir holding pnpm's .modules.yaml install record. This catches a modulesDir that came from pnpm's global config or the environment.
    • New pnpm_store_outside_project: true when a .modules.yaml in node_modules or a pnpm modules dir records a virtualStoreDir outside the project.
  • commands/vex.rs: the hosted lockfile_basis exemption no longer excuses package_not_found for pkg:npm/ purls when pnpm_store_outside_project holds. With nothing installed (no .modules.yaml) the lock basis still attests, as before.
  • CLI_CONTRACT.md (manifest-less VEX hosted row) and CHANGELOG.md document the new behavior.
  • No wrapper changes needed: npm/, pypi/ and gem/ only dispatch to the binary.

Test evidence

Red → green: each new test was run with the source fix reverted (tests kept) and failed, then passed with the fix.

Issue Test Without fix With fix
#661 core test_pnpm_modules_dir_is_a_crawl_root (workspace yaml, quoted/commented yaml key, .npmrc, .modules.yaml probe) FAILED ok
#661 core test_pnpm_modules_dir_setting_resolution (workspace member, yaml beats .npmrc, out-of-project values refused) FAILED ok
#661 cli in_process_alternate_installers::pnpm_modules_dir_install_is_patched (yaml and .npmrc) FAILED (apply exit 0, file unpatched: the reported symptom) ok
#661 cli in_process_alternate_installers::pnpm_modules_dir_install_then_apply_patches_file (real pnpm install; skips if pnpm < 10.12) n/a (new real-toolchain leg) ok on pnpm 10.28.0
#696 cli e2e_vex_redirect::pnpm_modules_dir_install_is_hash_verified_not_lockfile_attested FAILED (exit 0 attested) ok: hash_mismatch, exit 1; patched copy attests; nothing installed attests
#696 core test_pnpm_store_outside_project FAILED ok
#696 cli e2e_vex_redirect::pnpm_store_outside_project_is_not_lockfile_attested (GVS-style outside store, in-project store control, nothing-installed control) FAILED (exit 0 attested) ok

Local runs on Linux, Rust 1.93.1:

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo fmt: the changed files are fmt-clean. main itself isn't fmt-clean under this toolchain and CI doesn't run fmt, so I left unrelated files untouched.
  • cargo test --workspace --all-features --no-fail-fast: 9731 passed, 12 failed, 253 ignored. All 12 failures are write-failure / unremovable-file simulations that depend on chmod denying writes. This sandbox runs as root (uid 0), which bypasses that. The 4 core-lib ones (copy_tree::relax_loop_must_not_traverse_symlinked_root, vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry, pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched, pypi_requirements::wire_failure_rolls_back_already_written_files) fail identically on origin/main. The others are in covgap_commands_vendor, in_process_redirect and repair, none of which touch the npm crawler or the vex exemption. CI runs non-root.

Follow-ups / not covered

🤖 Generated with Claude Code

https://claude.ai/code/session_01JKwbXnQ94oRf1r94V1yupy


Note

Medium Risk
Changes npm discovery and hosted VEX attestation for pnpm layouts; incorrect handling could miss patches or over-attest, but behavior is narrowly scoped and heavily regression-tested.

Overview
Fixes pnpm modulesDir layouts (pnpm 10.12+ puts the virtual store under <modulesDir>/.pnpm with no node_modules). The npm crawler now treats configured modulesDir (pnpm-workspace.yaml / .npmrc) and child dirs with .modules.yaml as install roots, so agent apply patches those store copies instead of treating them as not installed (#661).

Hosted vex hash-checks installs under that layout. It also stops using lockfile-only attestation for pkg:npm/ when .modules.yaml records a virtualStoreDir outside the project (global virtual store or an escaping path), because transitive deps there are invisible to the crawler (#696). With nothing installed, lock-pin attestation is unchanged.

CLI_CONTRACT.md and CHANGELOG.md document the behavior; new unit and e2e tests cover crawl roots, setting resolution, apply, and vex outcomes.

Reviewed by Cursor Bugbot for commit f548ad0. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
pnpm 10.12+ with `modulesDir` set installs into `<modulesDir>/.pnpm`
and leaves no node_modules, so agent apply skipped every package as
"not installed" and exited 0 unpatched, and hosted vex attested
not_affected over the unpatched install. The crawler now treats the
configured modulesDir (pnpm-workspace.yaml or .npmrc), or a project
dir holding pnpm's .modules.yaml, as an install root.

Hosted vex also stops excusing a missing npm package as "nothing
installed" when pnpm keeps the installed virtual store outside the
project (global virtual store, or a virtualStoreDir that climbs
out), since transitive deps there are invisible to the crawler.

Fixes #661, #696.

Assisted-by: Claude Code:claude-opus-5-5
Adds a real-pnpm leg that installs with `modulesDir: deps` and checks
agent apply patches the `deps/.pnpm` store copy, and records the new
behavior in CLI_CONTRACT.md and the CHANGELOG.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 3, 2026 14: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 f548ad0. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

[burn-down agent] Ready for review at head f548ad0dc35c8a8cb55d03c71b79b311d17ded4b.

  • CI: 491/491 check runs green (success/skipped) on f548ad0.
  • Bugbot: reviewed f548ad0 — no findings; no unresolved review threads.
  • Mergeable: yes, up to date with main.
  • Reviewer focus: npm_crawler.rs modulesDir/.modules.yaml root detection, and the vex.rs change that stops lock-only attestation when virtualStoreDir escapes the project.

Slack announcement not sent this run (Slack send tool unavailable).


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit e5a6cbc into main Oct 5, 2026
492 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-pnpm-modules-dir-crawl branch October 5, 2026 11:23
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 5, 2026
Resolves conflicts with main's npm alias (#738), first-party link
(#634) and pnpm store (#698) changes:

- CLI_CONTRACT.md: keep main's hosted row and this PR's agent row.
- vex_consumed.rs: drop aliases the installed lookup already found
  (main), then store-expand only the new ones (this PR), so already
  expanded copies are not scanned again.
- Tests: keep both sides' new multicopy and e2e_vex regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174mrknEY9ge42c94RNRRBx
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

3 participants