DEV Community

Cover image for pnpm/action-setup: the step order setup-node needs, and one store cache
Kay
Kay

Posted on Originally published at latchkey.dev

pnpm/action-setup: the step order setup-node needs, and one store cache

pnpm/action-setup installs pnpm and puts it on PATH; it does not install Node.js, and since v4.3.0 it can cache the pnpm store itself. The order that works is checkout, then pnpm/action-setup, then actions/setup-node with cache: pnpm, because setup-node asks pnpm for its store path and fails if pnpm is not installed yet.

Four setup steps in order: checkout, pnpm/action-setup, setup-node with cache pnpm, pnpm install
Each step reads something the previous one produced. Read from pnpm/action-setup v6.1.1 and actions/setup-node v7.1.0 source on 2026-11-12.

Most pnpm workflows have three setup steps and one install, and most of the failures come from the order of the first three or from two of them disagreeing about a version. The action reads package.json before it installs anything, setup-node runs pnpm store path before it restores anything, and both can cache the same store with different keys.

The version comes from one of two places, the version input or packageManager in package.json, and setting both to strings that differ fails the step even when they agree on semver. Set neither and you get no pnpm version is specified, which has its own page.

Seven inputs, and an undeclared cache-hit

Seven inputs and two declared outputs. The restore step also sets a third output, cache-hit, which action.yml does not declare; it is true only when the exact key matched.

Input Default What it does
version empty Exact version, a range such as 10 or ^10.9.8, or latest. Required unless package.json names pnpm
dest ~/setup-pnpm Where the bootstrap pnpm is installed
run_install off Run pnpm install inside this step: true, an object or a list
cache false Cache the pnpm store with this action
cache_dependency_path pnpm-lock.yaml File or newline-separated files hashed into the cache key
package_json_file package.json Manifest to read, relative to the repository root; package.yaml also works
standalone false Install @pnpm/exe for pnpm 11 and earlier; no effect on pnpm 12

From action.yml, src/inputs and src/cache-restore/run.ts at v6.1.1 (commit 73e4699), read 2026-11-12. Outputs: dest and bin_dest.

Which pnpm version gets installed

With version empty, the action reads devEngines.packageManager first and packageManager second, stripping any +sha512 suffix. With version set and packageManager also present, it compares the two as plain strings and throws Multiple versions of pnpm specified: when they differ. That comparison is not semver: version: 12 against "packageManager": "pnpm@12.18.0" throws, even though 12.18.0 satisfies 12. Pick one source.

The install itself is a bootstrap pnpm from a lockfile committed in the action, followed by pnpm self-update to your version. For pnpm 11 and earlier, the action also checks the node on PATH and installs the standalone @pnpm/exe when that Node is older than 22.13, whatever standalone says. pnpm 12 ships a native executable and needs no Node at all.

# package.json carries "packageManager": "pnpm@12.18.0"
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
        # no version input: the manifest decides
Enter fullscreen mode Exit fullscreen mode

The step order, and why it is fixed

Checkout comes first whenever the version lives in package.json, because the action reads the file from the workspace and a missing file is silently treated as no version at all. pnpm/action-setup comes before setup-node whenever setup-node has cache: pnpm: setup-node finds the store with pnpm store path --silent, and without pnpm on PATH it stops with Unable to locate executable file: pnpm. A setup-node maintainer closed the report of that error as expected behavior, since pnpm must be installed before the setup-node step.

Keep the install as its own step after setup-node rather than using run_install. run_install runs inside the pnpm/action-setup step, so it runs before any later setup-node and uses whichever Node happened to be on PATH at that moment.

steps:
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  - uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
  - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
    with:
      node-version: 24
      cache: pnpm
  - run: pnpm install --frozen-lockfile
  - run: pnpm test
Enter fullscreen mode Exit fullscreen mode

Two caches for one store: keep one

Both actions cache the directory pnpm store path prints, and both hash pnpm-lock.yaml by default. They differ in the fallback and in the cleanup. setup-node passes no restore keys, so a lockfile change restores nothing. pnpm/action-setup passes a prefix restore key, so a lockfile change restores the previous store and pnpm downloads only the difference.

The README says the post step prunes the store for you. In the source it runs pnpm store prune only when run_install was set; with a separate install step it logs Pruning is unnecessary. and saves the store unpruned. Turning both caches on uploads the same directory twice under two keys.

One more reason the version matters for caching: the store path contains the store layout version, and an earlier bootstrap pnpm reported a different path from the one your pnpm wrote to, which broke both caches on a cold run. The action now self-updates to your version before asking for the path, and its source names issue #233 as the reason.

pnpm/action-setup cache: true setup-node cache: pnpm
Key pnpm-cache-<OS>-<arch>-<hash> node-cache-<OS>-<arch>-pnpm-<hash>
Restore fallback Prefix pnpm-cache-<OS>-<arch>- None
Store prune before save Only with run_install No
Step order constraint None beyond checkout pnpm must already be installed

Keys from src/cache-restore/run.ts in pnpm/action-setup v6.1.1 and src/cache-restore.ts in actions/setup-node v7.1.0, read 2026-11-12.

run_install, when you do want it

It accepts true (a recursive install), one object, or a list of objects, each with recursive, cwd and args. It is a convenience: a separate step after setup-node does the same work with the Node you chose, and the action exports PNPM_HOME either way, so global installs work in both places.

- uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
    with:
      run_install: |
        - recursive: true
          args: [--frozen-lockfile]
        - args: [--global, prettier]
Enter fullscreen mode Exit fullscreen mode

From v2 to pnpm 12 support

v2 stopped working with newer Node.js versions, and the README tells anyone on it to upgrade. For pnpm 11 and later, the same maintainers also publish pnpm/setup, which installs pnpm and a JavaScript runtime in one step; pnpm/action-setup still supports pnpm 12 alongside setup-node.

Release Change
v3.0.0 Runs on Node.js 20
v4.0.0 Throws when version and packageManager disagree
v4.2.0 Fetches pnpm from the registry in a root .npmrc
v4.3.0 cache input added
v5.0.0 Runs on Node.js 24
v6.0.0 pnpm 11 support
v6.1.0 pnpm 12 support

From the GitHub release notes for each tag, read 2026-11-12.

FAQ

Why does setup-node fail with Unable to locate executable file: pnpm?

Because cache: pnpm makes setup-node run pnpm store path --silent during its own step, and pnpm is not installed yet. Move pnpm/action-setup above setup-node, or drop cache: pnpm and use the action's own cache: true, which has no ordering constraint beyond checkout.

How does pnpm/action-setup compare to run: corepack enable?

Both get a pinned pnpm onto the runner. A pnpm maintainer answered in the action's issue tracker that there is nothing wrong with corepack, and that pnpm's docs at the time recommended it for every CI except GitHub Actions. The action adds the store cache, the version input and the standalone executable on old Node.

Is the action's cache setup equivalent to actions/setup-node's pnpm cache?

They cache the same directory with different keys and different fallbacks. The action restores the previous store on a lockfile change through a prefix restore key; setup-node passes no restore key and starts cold. Use one of them, not both.

Why does pnpm/action-setup throw Multiple versions of pnpm specified?

Because version and packageManager are both set and are not the same string. A major-only version: 12 against pnpm@12.18.0 counts as a mismatch. Remove version to let the manifest decide, which is also what your local tooling reads.

References

Top comments (0)