Skip to content

pnpm-workspace.yaml edits miss quoted or space-before-colon top-level keys, append a duplicate trustLockfile/overrides key, and break every install #402

Description

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

Summary

Both line-splice editors of pnpm-workspace.yaml find an existing top-level key only when the line literally starts with trustLockfile: or overrides:. Valid YAML spellings of the same key are missed:

  • a double-quoted key: "trustLockfile": false, "overrides":
  • a single-quoted key: 'trustLockfile': true
  • a space before the colon: trustLockfile : false

When the key is missed, scan --mode hosted appends a second trustLockfile: true, and vendor appends a second overrides: section. pnpm rejects duplicate mapping keys, so the workspace file no longer loads, and every later pnpm install (frozen or not) fails. Both commands report success.

Impact

  • The repo's installs break after a "successful" run.
  • In the "trustLockfile": false case, the user's explicit opt-out is overridden rather than respected. CLI_CONTRACT.md says the trust write "preserves explicit user settings", and plan_workspace_trust has a UserSet branch for exactly this case. It just never matches.
  • In vendored mode, the user's own quoted overrides section is treated as absent, so the pre-flight conflict check (check_workspace_override) never examines it.

This is a different defect from #400 (non-block document shapes); here the document is an ordinary block mapping.

Repro

echo '{"name":"proj","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
printf 'packages:\n  - '"'"'.'"'"'\n"trustLockfile": false\n' > pnpm-workspace.yaml
pnpm install                                     # loads fine
socket-patch scan --mode hosted --yes --json --api-url $MOCK --org test-org --api-token fake
#   exit 0, status success, rewrittenFiles [pnpm-lock.yaml, pnpm-workspace.yaml]
cat pnpm-workspace.yaml
#   packages:
#     - '.'
#   "trustLockfile": false
#   trustLockfile: true
pnpm install --frozen-lockfile
#   [ERROR] duplicated mapping key (4:1)

Vendored (.socket/manifest.json and blobs staged):

printf 'packages:\n  - '"'"'.'"'"'\n"overrides":\n  is-number: 7.0.0\n' > pnpm-workspace.yaml
pnpm install && socket-patch vendor --yes --json --offline   # exit 0, applied
#   ... "overrides":\n  is-number: 7.0.0\noverrides:\n  [email protected]: file:.socket/vendor/...
pnpm install --frozen-lockfile                                # duplicated mapping key (5:1)

$MOCK is a local mock of the patch API (batch / by-package / package grant / view routes plus the tarball), modelled on crates/socket-patch-cli/tests/e2e_redirect_pnpm_build.rs.

Expected vs actual

  • Expected: a key pnpm reads as trustLockfile / overrides is recognised as that key. An explicit non-true trustLockfile is kept, with the redirect_pnpm_trust_lockfile warning, as the UserSet branch intends. An existing overrides mapping is edited in place, or the run refuses with a clear code if the splice can't edit it, as is already done for an inline overrides: mapping.
  • Actual: a duplicate key is appended, the file can't be parsed, and the command reports success.

OS × version (Linux, Node 22, main f6b7fb9)

pnpm hosted "trustLockfile": false hosted 'trustLockfile': true hosted trustLockfile : false vendored "overrides":
10.34.5 fail (duplicated mapping key) not run not run not run
11.27.0 fail fail fail fail
12.8.1 fail (duplicate mapping) not run not run fail (duplicate mapping)
control: unquoted trustLockfile: false (documented UserSet behaviour; not re-run this time)

Released 4.0.0 behaves the same (hosted "trustLockfile": false, pnpm 11.27.0). It isn't a regression. macOS and Windows weren't probed (pure string matching).

Suspect code

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:393-400: line.strip_prefix("trustLockfile:") is the only way an existing key is detected.
  • crates/socket-patch-core/src/vendor/pnpm_lock.rs:1667 (ws_overrides_section → section_bounds(lines, "overrides")) and the inline check at :1695 (l.starts_with("overrides:")).

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