Skip to content

Hosted scan/get run from a pnpm workspace member (or with lockfile-dir=..) ignores the parent pnpm-lock.yaml and reports success while pinning nothing #590

Description

[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).

Summary

pnpm keeps one pnpm-lock.yaml at the workspace root, and pnpm commands work the same from any member directory. If you run socket-patch scan --mode hosted, or get <uuid> --mode hosted, with the cwd inside a member such as packages/a, hosted mode only looks for a lock in the cwd. It finds none, rewrites nothing, and exits 0 with status: success, redirected: 0. The only warning is redirect_npm_no_lockfile ("no package-lock.json / npm-shrinkwrap.json present"), which is wrong for a pnpm project: the lock exists one or two directories up.

A non-workspace project using pnpm's lockfile-dir=.. (.npmrc) / lockfileDir: .. (pnpm-workspace.yaml) gives the same result. pnpm writes the lock to the parent directory, and a hosted scan from the project directory succeeds while pinning nothing.

Impact

Repro (Linux, pnpm 12.8.1; the same on 9.15.9 / 10.34.5 / 11.28.3)

The patch API is a local mock that serves a free patch for pkg:npm/is-number@7.0.0 (batch, by-package, view, package grant, and the hosted tarball). SOCKET_PATCH_SERVER_URL points at the mock.

mkdir -p r/packages/a && cd r
echo '{"name":"r","private":true}' > package.json
printf 'packages:\n  - packages/*\n' > pnpm-workspace.yaml
echo '{"name":"a","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}' > packages/a/package.json
pnpm install
cd packages/a
socket-patch scan --mode hosted --json --yes --cwd . --api-url $MOCK --org test-org --api-token fake
#   exit 0, status success, redirected 0, warnings [redirect_npm_no_lockfile]
socket-patch get <uuid> --mode hosted --json --yes --cwd . ...   # same
cd ../.. && git diff --stat pnpm-lock.yaml                          # unchanged
# a fresh checkout + pnpm install --frozen-lockfile -> packages/a/node_modules/is-number is upstream
socket-patch scan --mode hosted --json --yes --cwd . ...            # from the root: redirected 1 (works)

The lockfile-dir variant:

mkdir -p top/proj && cd top/proj
echo '{"name":"proj","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}' > package.json
echo 'lockfile-dir=..' > .npmrc        # pnpm 10+: also `lockfileDir: ..` in pnpm-workspace.yaml
pnpm install                           # writes ../pnpm-lock.yaml
socket-patch scan --mode hosted --json --yes --cwd . ...   # exit 0, success, redirected 0, redirect_npm_no_lockfile

Expected vs actual

  • Expected: one of these. (a) Hosted mode finds the governing lock the way pnpm does: the nearest ancestor pnpm-workspace.yaml, or a configured lockfile-dir. Or (b) it refuses, with a non-success status and a pnpm-specific diagnostic that says to run from the directory holding pnpm-lock.yaml, the way vendored mode already fails closed (vendor_lockfile_missing). CLI_CONTRACT treats a found patch that wasn't applied as a non-success outcome, and the warning should name the right package manager. The code already does that for redirect_pnpm_no_lockfile when node_modules/.modules.yaml is present.
  • Actual: success, exit 0, nothing pinned, and an npm-specific "no package-lock.json" warning. The member's node_modules/.modules.yaml isn't there (pnpm keeps it at the root), so even the pnpm-named warning doesn't fire.

OS × version

OS pnpm member cwd: scan member cwd: get <uuid> lockfile-dir=.. from root (control)
Linux 9.15.9 fail (2/2) fail fail pass
Linux 10.34.5 fail fail fail pass
Linux 11.28.3 fail fail n/a pass
Linux 12.8.1 fail (2/2) fail fail pass
macOS / Windows — untested (path-independent logic, expected the same)

First bad version: not a regression. Release 4.0.0 behaves the same, and so does main 203e092.

Suspect code

  • crates/socket-patch-core/src/hosted/engine.rs:404 (read_candidate_files) reads lock candidates relative to --cwd only. There's no ancestor or lockfile-dir lookup.
  • crates/socket-patch-core/src/patch/redirect/mod.rs:826-857 falls through to redirect_npm_no_lockfile and keeps the run a success.

Related: #417 (cargo, same shape), #492 (pnpm per-package locks).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions