Skip to content

Hosted and vendored pnpm 11/12 refuse a standalone project nested under an unrelated pnpm-workspace.yaml (not in its packages: globs) as a "workspace member", and the suggested fix doesn't work (regression from #888) #1006

Description

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

Summary

Since #888 (fix for #880 / #881), hosted and vendored modes refuse any pnpm project with its own v9 pnpm-lock.yaml and no pnpm-workspace.yaml of its own whenever any ancestor directory has a pnpm-workspace.yaml. governing_workspace_file takes the nearest ancestor file without checking that the project matches that file's packages: globs.

pnpm 11 and 12 don't treat such a directory as a workspace project. Running pnpm install in examples/demo (not matched by the root's packages: [packages/*]) writes examples/demo/pnpm-lock.yaml and reads only a pnpm-workspace.yaml in examples/demo, never the root one. This is a common layout: examples/, e2e/ or docs/ apps outside the globs, or a checkout inside a parent folder that happens to hold a pnpm-workspace.yaml.

So on main:

  • hosted exits 1 with redirect_pnpm_settings_elsewhere. Its remedy ("add trustLockfile: true to /pnpm-workspace.yaml") does nothing: the frozen install still fails with ERR_PNPM_TARBALL_URL_MISMATCH.
  • vendored fails each package with vendor_pnpm_settings_elsewhere and tells the user to use --mode hosted, which also refuses. Neither mode can patch the project.
  • Release 4.0.0 patches the same project in both modes. The nested pnpm-workspace.yaml it creates (which the new refusal says "would be ignored") is exactly the file pnpm reads, and a fresh frozen install lands patched bytes.

Impact

Every pnpm 11/12 project nested below an unrelated workspace file stops being patchable in hosted and vendored modes, and both error messages point at remedies that don't work.

Repro (pnpm 12.10.1, Node 22, mock patch API as in the pnpm compatibility e2e)

mkdir -p ws/packages/a ws/examples/demo && cd ws
printf 'packages:\n  - packages/*\n' > pnpm-workspace.yaml
echo '{"name":"root","private":true}' > package.json
echo '{"name":"a","version":"1.0.0"}' > packages/a/package.json
echo '{"name":"demo","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0","ms":"2.1.3"}}' > examples/demo/package.json
cd examples/demo && pnpm install          # pnpm 11/12: lock written to examples/demo/pnpm-lock.yaml
socket-patch scan --mode hosted --json --yes     # exit 1, redirect_pnpm_settings_elsewhere
socket-patch scan --mode vendored --json --yes   # exit 1, vendor_pnpm_settings_elsewhere x2

# Proof that the root file doesn't govern demo (pins written with --no-trust-lockfile-config,
# then a fresh frozen install of a copy, empty store):
#  C: no trust anywhere                                   -> ERR_PNPM_TARBALL_URL_MISMATCH
#  A: `trustLockfile: true` in ws/pnpm-workspace.yaml     -> ERR_PNPM_TARBALL_URL_MISMATCH (the advised fix)
#  B: `trustLockfile: true` in examples/demo/pnpm-workspace.yaml -> exit 0, left-pad patched

Expected vs actual

CLI_CONTRACT.md scopes both codes to "a pnpm workspace member with its own … pnpm-lock.yaml (sharedWorkspaceLockfile: false)". examples/demo isn't a member: pnpm doesn't list it as a workspace project, gives it its own lock, and reads its own pnpm-workspace.yaml. Expected: hosted pins the lock and creates examples/demo/pnpm-workspace.yaml with trustLockfile: true, and vendored wires the override there, as 4.0.0 does. Actual: both modes refuse, and nothing is written.

OS × version

OS pnpm layout (lock location) hosted on main vendored on main 4.0.0
Linux 12.10.1 own lock in examples/demo refused (reproduced twice), advised fix fails, nested file works refused hosted + vendored pass, fresh frozen install patched
Linux 11.28.5 own lock in examples/demo refused, advised fix fails, nested file works not run not run
Linux 10.34.6 / 9.15.9 pnpm installs the root workspace instead (no lock in demo) n/a n/a n/a

macOS and Windows weren't probed. The logic is path-only and OS-independent.

First bad commit

8e8daf0 (#888). Release 4.0.0 is good.

Suspect code

  • crates/socket-patch-core/src/utils/pnpm_workspace.rs:27 (governing_workspace_file): any ancestor file is treated as governing; the project isn't matched against its packages: globs (or the root itself).
  • crates/socket-patch-core/src/hosted/governing_root.rs:139 (pnpm_settings_elsewhere)
  • crates/socket-patch-core/src/vendor/pnpm_lock.rs:594 (vendored refusal)

No probe runs; Linux only.


Backlog review — 2026-10-08

Priority: P1 → P2. An unrelated ancestor workspace causes a false refusal. Real pnpm usability regression with a narrow nesting trigger.

Activity

  1. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  2. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1. pnpm. Already claimed by draft #1007.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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.

  4. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] New variant from the pnpm bug-hunt routine (ledger #303), main cf8b164, Linux, reproduced twice: a settings-only parent pnpm-workspace.yaml with no packages: key gives the same wrong refusal. PR #1007's regression tests use parents that do have packages: globs, so this shape may need its own case.

    Layout: parent/pnpm-workspace.yaml contains only minimumReleaseAge: 0, and parent/child/ is a standalone project depending on left-pad@1.3.0. Running pnpm install in child/ on pnpm 11.28.5 and 12.10.1 writes child/pnpm-lock.yaml, not a lock at the parent, so pnpm treats the child as its own root.

    pnpm scan --mode hosted in child/ scan --mode vendored Suggested fix (trustLockfile: true in the parent file) → fresh --frozen-lockfile trustLockfile: true in child/pnpm-workspace.yaml → fresh --frozen-lockfile
    11.28.5 exit 1, redirect_pnpm_settings_elsewhere not run ERR_PNPM_TARBALL_URL_MISMATCH pass, patched bytes
    12.10.1 exit 1, redirect_pnpm_settings_elsewhere exit 1, vendor_pnpm_settings_elsewhere ERR_PNPM_TARBALL_URL_MISMATCH pass, patched bytes

    So, as in the original report, the refusal sends the user to a file pnpm doesn't read for this project. A parent file with no packages: key should probably count as "not a member" for pnpm ≥ 11, just like a parent whose globs don't match.

    On pnpm 10.34.6 the same layout installs nothing in child/ (no lock, no node_modules). Hosted and vendored then exit 0 with nothing to do, so pnpm 10 isn't affected.


    Generated by Claude Code

  5. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] pnpm bug-hunt, another variant on main 03b9418: a project excluded by a negated glob.

    # pnpm-workspace.yaml at the root
    packages:
      - 'packages/*'
      - '!packages/excluded'

    packages/excluded (left-pad@1.3.0) gets its own pnpm-lock.yaml from pnpm install run inside it, and the root lock has no left-pad. pnpm treats it as outside the workspace.

    pnpm scan --mode hosted from packages/excluded scan --mode vendored
    11.28.5 exit 1, redirect_pnpm_settings_elsewhere, nothing written not run
    12.10.1 exit 1, redirect_pnpm_settings_elsewhere, nothing written exit 1, vendor_pnpm_settings_elsewhere

    The refusal's remedy is wrong here too (12.10.1, pinned with --no-trust-lockfile-config, then a frozen install with an empty store):

    • trustLockfile: true appended to the root pnpm-workspace.yaml, as the message suggests: ERR_PNPM_TARBALL_URL_MISMATCH.
    • trustLockfile: true in a new packages/excluded/pnpm-workspace.yaml, which the message says "would be ignored": the install succeeds and left-pad is patched.

    So the membership check needs to honour ! patterns in packages: as well as plain globs.


    Generated by Claude Code

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