Skip to content

Hosted and vendored modes create a packages: ['.'] pnpm-workspace.yaml that turns a single-package project into a workspace, so pnpm add <pkg> fails with ERR_PNPM_ADDING_TO_ROOT on pnpm 9.0–10.4 #734

Description

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

Summary

For any project with a lockfileVersion 9.0 pnpm-lock.yaml and no pnpm-workspace.yaml, scan --mode hosted creates the scaffold

packages:
  - '.'
trustLockfile: true

and scan --mode vendored creates the same packages: ['.'] scaffold with an overrides: block. On pnpm 9.0.0 through 10.4.1, that file makes pnpm treat the plain single-package project as a workspace whose only member is the root. The most common day-to-day command, pnpm add <pkg>, then fails:

ERR_PNPM_ADDING_TO_ROOT  Running this command will add the dependency to the workspace root, which might not be what you want - if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). ...

Nothing is added to package.json or the lock. Before the scan, the same pnpm add works.

The packages: key can't just be dropped. pnpm 9 refuses a workspace file without it (ERROR packages field missing or empty, checked below), and the code comment at guidance.rs says the scaffold exists for that reason. On these versions the hosted half is also unnecessary: the docs say pnpm <= 10 doesn't need trustLockfile ("pnpm >=11 needs this for hosted URLs … pnpm <=10 does not need the setting", docs/ecosystems.md). So hosted mode changes the project's pnpm semantics for a setting those pnpm versions don't use.

Impact

  • Every developer and CI job on pnpm 9.x (and 10.0–10.4) that later runs pnpm add <pkg> in a project socket-patch touched gets a hard error. The error message talks about workspaces, not socket-patch, so the link to the scan isn't obvious.
  • This affects hosted mode, which is the v5 default for a bare scan, and vendored mode.
  • The workaround is pnpm add -w <pkg> or ignore-workspace-root-check=true. With -w the hosted pin survives, which I checked on 9.15.9.

Repro (Linux, Node 22)

mkdir app && cd app
printf '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}\n' > package.json
npx pnpm@9.15.9 install
npx pnpm@9.15.9 add is-number@7.0.0      # control: works (then revert, or use a fresh dir)

# fresh dir, same package.json + install, then:
socket-patch scan --mode hosted --yes     # (or --mode vendored)
cat pnpm-workspace.yaml                   # packages: ['.'] + trustLockfile: true
npx pnpm@9.15.9 add is-number@7.0.0       # ERR_PNPM_ADDING_TO_ROOT, exit 1, nothing added

(I used a local mock of the patch API serving a real patched left-pad@1.3.0 tarball. Any granted pnpm 9.0 lock pin reproduces it.)

Control showing why packages: is needed on pnpm 9:

printf 'trustLockfile: true\n' > pnpm-workspace.yaml
npx pnpm@9.15.9 add is-number@7.0.0       # ERROR packages field missing or empty
npx pnpm@9.15.9 install --frozen-lockfile # ERROR packages field missing or empty

Expected vs actual

  • Expected: CLI_CONTRACT says the hosted write "preserves user bytes" and that installs "need no extra flags". It describes the scaffold only as a vehicle for trustLockfile, which pnpm >= 11 requires. Ensuring trust (or vendored overrides) shouldn't change how the user's own pnpm commands behave, and on pnpm <= 10 hosted mode doesn't need the file at all.
  • Actual: after a successful scan, pnpm add <pkg> exits 1 with ERR_PNPM_ADDING_TO_ROOT on pnpm 9.0.0 – 10.4.1.

Matrix (Linux, socket-patch main 045d7ec; pnpm add is-number@7.0.0 after the scan)

pnpm hosted vendored control (no scan)
9.0.0 fail ADDING_TO_ROOT untested pass
9.7.1 fail untested pass
9.15.9 fail (3×) fail pass
10.0.0 fail (2×) fail pass
10.1.0 / 10.2.1 / 10.3.0 / 10.4.1 fail untested pass
10.5.0 / 10.5.2 / 10.12.1 / 10.34.5 pass pass (10.5.0) pass
11.0.0 / 11.28.3 / 12.8.1 pass (see note) untested pass
7.33.7 / 8.15.9 n/a (legacy locks get no scaffold) n/a pass

pnpm 10.5.0 is the first pnpm release that no longer refuses in a root-only workspace.

Note, not part of this bug: on pnpm 11.0.0 / 11.28.3, pnpm add <other> succeeds but re-resolves the hosted pin back to upstream. pnpm 9, 10 and 12 keep it. That's the same pnpm 11 re-resolution recorded in #713.

Not a regression: release 4.0.0 writes the same scaffold and fails the same way on pnpm 9.15.9. 3.3.0 has no hosted mode.

Suspect code

  • crates/socket-patch-core/src/hosted/guidance.rs:252: TrustPlan::Create("packages:\n - '.'\ntrustLockfile: true\n") runs for every 9.0 root lock, whatever pnpm version the project uses.
  • crates/socket-patch-core/src/vendor/pnpm_lock.rs:95: WS_SCAFFOLD_PACKAGES, the same root-only scaffold on the vendored side.

Possible directions (for maintainers): skip creating the hosted scaffold when the project pins pnpm <= 10 (packageManager, engines, devEngines), since pnpm <= 10 doesn't need the key; or also write ignore-workspace-root-check=true (.npmrc) / ignoreWorkspaceRootCheck: true when socket-patch creates the scaffold; or at least document the -w requirement in the redirect_pnpm_trust_lockfile detail and in pnpm-compatibility.md.

No probe runs: this is Linux-only evidence. The behaviour comes from pnpm's own workspace detection, so I don't expect it to depend on the OS.

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions