Skip to content

Update pnpm to the latest 12.x release #2155

Closed

Description

@kmcginnes

pnpm was pinned at 11.9.0 via packageManager in the root package.json. It was left alone in the dependency sweep because it does not behave like an ordinary manifest bump. Target is the latest 12.x, currently 12.4.2.

Why it was held back

pnpm records its own version inside the lockfile, in a packageManagerDependencies block under the root importer:

importers:
  .:
    configDependencies: {}
    packageManagerDependencies:
      '@pnpm/exe':
        specifier: 11.9.0
        version: 11.9.0
      pnpm:
        specifier: 11.9.0
        version: 11.9.0

Editing packageManager by hand does not update that block. pnpm install, pnpm install --lockfile-only, and pnpm install --force all report "Already up to date" and leave the old specifiers in place, along with the @pnpm/exe and per-platform @pnpm/* entries in packages:. Deleting the block by hand does not help either, because nothing regenerates it and the orphaned entries are not pruned.

Resolved: corepack use pnpm@<version> is the procedure. It rewrites packageManager with a correct +sha512 hash and then runs a real install, which regenerates the block and prunes the orphans in one step.

Work

  • Bump packageManager to pnpm@12.4.2 with a matching +sha512.<hex> integrity hash. Generate it with corepack use; do not hand-write it. The hash is the hex encoding of npm's base64 dist.integrity, not the base64 itself.
  • Update the pnpm requirement in docs/development.md.
  • Get the lockfile's packageManagerDependencies block and the @pnpm/exe.* entries onto 12.4.2 consistently, with the pnpm 11 entries removed.
  • Move .github/workflows/unit.yml to a pnpm/action-setup release that supports pnpm 12. v5 does not; v6.1.0 added it.
  • Pin the Dockerfile's corepack@latest to an exact version. The container acquires pnpm only through corepack enable, and pnpm 12 ships as a native executable, so the Corepack version is coupled to the pnpm major.
  • Remove engines.pnpm. pnpm 12 does not enforce a root project's engines.pnpm at all, so it is a declarative range duplicating the exact version packageManager already pins. npm and Yarn are already blocked without it, npm by EUNSUPPORTEDPROTOCOL on the workspace:* dependencies and Yarn by packageManager naming pnpm.
  • Do not add a check comparing the +sha512 hash against the lockfile. This was originally required here on the premise that nothing verifies the hash. That premise is wrong; see "Verification, corrected" below.

Verification, corrected

A check was built for this and then removed, because testing it disproved its premise.

pnpm never reads the +sha512 hash. With packageManager set to pnpm@12.3.4+sha512. followed by 128 zeros and a correct lockfile, a cold-cache install still downloads and runs the pinned version. What verifies the bytes is the install path: pnpm resolves through pnpm-lock.yaml and checks each download against npm's registry signature, so corrupting the lockfile's integrity while leaving the manifest hash correct stops the install with "Refusing to run pnpm@12.3.4: its npm registry signature could not be verified". Corepack is the tool that does read the hash, and it fails closed on a mismatch. So both install paths verify what they fetch, and the hash stays in packageManager because Corepack depends on it.

The check also could not catch the case that matters. corepack use and pnpm install write the manifest hash and the lockfile integrity from separate downloads, which was the argument for comparing them. But if both ran against the same compromised registry, both values come from the same bad bytes and the comparison passes.

One related result, which narrows the "Why it was held back" section above rather than contradicting it. That section records pnpm install leaving packageManagerDependencies stale, observed with pnpm 11.9.0 pinned. Under pnpm 12 it does not: with the pin at 12.4.2 and the lockfile recording 12.3.4, pnpm install rewrote the block and every @pnpm/exe.* entry to 12.4.2. It did so under --frozen-lockfile too, and still exited 0, which is worth knowing separately. pnpm 11's behaviour was not retested, so the original observation stands as recorded. Either way corepack use remains the procedure, because it is the only thing that writes a correct hash.

Full reproduction output is in this comment on #2178.

Not in scope

Whether the repo should rely on pnpm's own version management or on Corepack was originally listed here. Local development is settled: docs/development.md leads with letting pnpm switch itself, because that route resolves through the lockfile-pinned @pnpm/exe.* integrity, and presents Corepack as the fallback. How the container acquires Node and pnpm is a larger question and belongs to #2158; pinning the existing corepack@latest to an exact version is the conservative baseline that issue names, and is in scope here.

Acceptance

packageManager, pnpm-lock.yaml, and docs/development.md all agree on 12.4.2; engines.pnpm is gone; the Dockerfile pins an exact Corepack version; pnpm install --frozen-lockfile succeeds from a clean checkout; pnpm checks and pnpm test pass; CI is green.

Related Issues

Activity

  1. added
    tech debtIssues, typically tasks, that are mainly about cleaning up code that is problematic in some way
    internalSignals that the team will work on this issue internally.
    dependenciesPull requests that update a dependency file
    on Sep 10, 2026
  2. added theissue type on Sep 10, 2026
  3. self-assigned this
    on Sep 14, 2026
  4. changed the title [-]Update pnpm to the latest 11.x release[/-] [+]Update pnpm to the latest 12.x release[/+] on Sep 14, 2026
  5. added this to the Release v3.3 milestone on Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

dependenciesPull requests that update a dependency fileinternalSignals that the team will work on this issue internally.tech debtIssues, typically tasks, that are mainly about cleaning up code that is problematic in some way

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions