Repository navigation
Agent-mode scan packages/<member> finds nothing in a pnpm workspace (exit 0), while rollback packages/<member> selects the same packages #778
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
In a pnpm workspace with the default isolated linker, scan --mode agent <PATH> can't scope to a workspace member. scan --mode agent packages/a (also packages/a/** and packages/a/node_modules/left-pad) reports No installed packages found under the given path: packages/a., scans 0 packages and exits 0, even though packages/a depends on left-pad@1.3.0 and a patch for it exists. An unscoped scan, or scan --mode agent node_modules/.pnpm, finds and patches it.
rollback packages/a uses the same pattern and does select left-pad. Its results list both copies: ./node_modules/.pnpm/left-pad@1.3.0/node_modules/left-pad and ./packages/a/node_modules/left-pad. So the two commands that CLI_CONTRACT says share glob semantics disagree on the most common monorepo scope.
The cause looks like this. Every pnpm workspace member's node_modules/<dep> is a symlink into the root node_modules/.pnpm. The npm crawler's seen name@version dedup records each package once, at the first path it meets, and the root .pnpm store is walked before the member trees. Scan's path filter then sees only the .pnpm copy. Rollback's find_all_packages_for_rollback returns every copy, including the member link.
Impact
- The CLI_CONTRACT example
scan packages/foo(andapps/**) never selects anything in a pnpm workspace. CI that patches per member withsocket-patch scan --mode agent --json packages/<name>getsstatus: success,scannedPackages: 0, exit 0, and the member's vulnerable dependency stays unpatched with no warning. - Patch and undo aren't symmetric:
rollback packages/areverts packages thatscan packages/acould never have applied. - A single-package pnpm project isn't affected: the root
node_modules/left-padlink is recorded first, soscan node_modules/left-padmatches. Neither isnode-linker=hoisted(members have nonode_modules).
Repro (pnpm 12.8.1, Linux, main 045d7ec)
mkdir -p ws/packages/a ws/packages/b && cd ws
echo '{"name":"root","version":"1.0.0","private":true}' > package.json
printf "packages: ['packages/*']\n" > pnpm-workspace.yaml
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > packages/a/package.json
echo '{"name":"b","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}' > packages/b/package.json
pnpm install
ls -l packages/a/node_modules/ # left-pad -> ../../../node_modules/.pnpm/left-pad@1.3.0/node_modules/left-pad
# patch API serving a patch for pkg:npm/left-pad@1.3.0 (local mock used here)
socket-patch scan --mode agent --dry-run --json packages/a # scannedPackages 0, packagesWithPatches 0
socket-patch scan --mode agent --yes packages/a # "No installed packages found under the given path: packages/a." exit 0
socket-patch scan --mode agent --dry-run --json node_modules/.pnpm # scannedPackages 2, packagesWithPatches 2
socket-patch scan --mode agent --dry-run --json # scannedPackages 2, packagesWithPatches 2
# rollback with the same pattern (after an unscoped apply of a local manifest + blobs)
socket-patch apply --offline
socket-patch rollback --offline --dry-run --json packages/a
# "manifest": {"removedEntries": ["pkg:npm/left-pad@1.3.0"]}
# results[].path: ./node_modules/.pnpm/left-pad@1.3.0/node_modules/left-pad, ./packages/a/node_modules/left-padExpected vs actual
CLI_CONTRACT.md, "Path-scoped scans": in agent mode, "a package is in scope iff ANY of its crawled installed copies sits under a matching path", "a pattern matching any ancestor directory of the copy path also matches, so a bare scan packages/foo scopes the whole subtree". It also says the glob semantics are "shared with rollback's path targets".
- Expected:
scan --mode agent packages/aselectsleft-pad@1.3.0(its installed copy for memberaispackages/a/node_modules/left-pad), just asrollback packages/adoes. - Actual: scan sees only the deduplicated
.pnpmpath. The scope is empty and the run exits 0 with nothing patched.
OS × version
| OS | pnpm | scan packages/a (and /**, and the link path) |
rollback packages/a |
|---|---|---|---|
| Linux | 8.15.9 (lock 6.0) | 0 packages, exit 0 | selects |
| Linux | 9.15.9 | 0 packages, exit 0 | selects |
| Linux | 10.34.5 | 0 packages, exit 0 | selects |
| Linux | 12.8.1 | 0 packages, exit 0 (reproduced 2×) | selects (both copies listed) |
| Linux | 10.34.5 node-linker=hoisted |
n/a: there's no member node_modules |
n/a |
| Linux | 12.8.1 single-package project, scan node_modules/left-pad |
pass (1 package) | selects |
macOS and Windows weren't tested, but the crawl order and dedup aren't OS-specific.
First bad version
This isn't a regression. Release 4.0.0 has no scan [PATHS]; path-scoped scans arrived with v5 on main.
Suspect code
crates/socket-patch-cli/src/commands/scan/mod.rs:1750: the scope filter testspkg.pathof the crawled records, which hold one path per name@version.crates/socket-patch-core/src/crawlers/npm_crawler.rs:1811: theseenname@version dedup. The root.pnpmstore wins before the workspace member links are visited.crates/socket-patch-cli/src/commands/rollback.rs:1374: rollback matches againstfind_all_packages_for_rollback, which returns every copy, including member links. That's why the two commands diverge.
Other symlinked layouts (yarn's pnpm linker, Bun's isolated linker, vlt, npm install-strategy=linked) probably behave the same way. Each sibling routine can confirm its own.
Backlog review — 2026-10-08
Priority: P1 → P2. Agent scan with a workspace-member path filter matches only the store path and misses member-linked copies. Keep this path-scoping/discovery defect in #1007; it is not about the executable PATH.
Activity
mikolalysenko commentedon Oct 4, 2026
[agent] Triaged as
priority:p1(pnpm). No duplicate or open fix PR found. It's related to the workspace-member scoping issues #590 and #492, but the cause is different: here the agent-mode crawler dedups by name@version before scan's path filter runs, and those issues are about hosted lock discovery.Generated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] Yarn classic bug-hunt run 25 (ledger #304): the same scope miss happens in a yarn classic workspace with no symlinks at all. A fix that only teaches the crawler about member links won't cover it.
Layout: the root depends on
left-pad@1.2.0. Memberspackages/aandpackages/beach depend onleft-pad@1.3.0, so yarn 1 installs two real directory copies,packages/a/node_modules/left-padandpackages/b/node_modules/left-pad, with the rootnode_modules/left-padat 1.2.0.packages/a/**,./packages/a, the absolute path andpackages/a/node_modules/left-padall scan 0 packages too.9c43dfc)scan packages/ascan packages/brollback --dry-run packages/aSame root as the pnpm case: the npm crawler's
seenname@version dedup keeps one record per purl, and the scope filter inscan/mod.rstests only that record'spkg.path. Hereb's copy wins even thoughasorts first, so which member is scopeable seems to follow directory-walk order rather than anything the user controls. The contract says "a package is in scope iff ANY of its crawled installed copies sits under a matching path", so the filter needs every copy's path, not just the surviving record's.npm workspaces with a version conflict produce the same nested-copy layout and probably behave the same way. I haven't run that here.
Generated by Claude Code
yarn-classic bug-hunt run 25: PR verifications, #778/#884 comments
mikolalysenko commentedon Oct 7, 2026
[agent] Claiming this issue as part of the pnpm open-issue sweep in draft PR #1007. Branch: agent/fix-pnpm-open-issues. Claim-ID: 2026-10-07T12:44:29Z-pnpm07
Fix agent-mode scan PATH scope missing workspace member copies (#778)
Fix duplicate apply/rollback events for pnpm workspace member links (#…
mikolalysenko commentedon Oct 7, 2026
[agent] PR #1007 fixes this issue. It has a regression test that fails on
main, CI is fully green (552 checks) and it's ready for review. The issue will close when #1007 merges. The PR description's table gives the root cause, the fix and the test for each issue.