DEV Community

Cover image for actions/setup-node: version files, the cache that turns itself on, and publishing
Kay
Kay

Posted on Originally published at latchkey.dev

actions/setup-node: version files, the cache that turns itself on, and publishing

actions/setup-node installs a Node.js version, puts it on PATH, can restore your package manager's global cache, and can write an .npmrc for publishing. Since v6 it caches npm on its own when package.json names npm as the package manager, and since v7 (v7.1.0 is current) it no longer exports a placeholder NODE_AUTH_TOKEN when you set registry-url.

Flow of three questions deciding whether setup-node caches, beside the cache key and its rules
Whether setup-node v7 caches at all, and the key it uses when it does. Exact match only, saved only when the job succeeds.

Most workflows carry a setup-node step copied from somewhere else, with a cache: npm line nobody has checked. Then a major version lands and something shifts: caching turns itself on, a pnpm project fails before pnpm is installed, or an OIDC publish breaks on a token nobody set.

Two of those shifts came from caching, whose rules moved twice in two months between v5 and v6, and one from publishing, where v7 stopped exporting a placeholder token. If your workflow predates v6, read the caching section before you bump the pin.

Inputs, and the two new cache outputs

The table is action.yml at the v7.1.0 tag. Four outputs come back: node-version, cache-hit, and, new in v7, cache-primary-key and cache-matched-key, which let a later step see exactly which entry was restored. The cache is saved in a post step that action.yml limits to success(), so a failed job saves nothing for the next run.

permissions:
  contents: read
steps:
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
      persist-credentials: false
  - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
    with:
      node-version-file: .nvmrc
      cache: npm
  - run: npm ci
  - run: npm test
Enter fullscreen mode Exit fullscreen mode
Input Default What it does
node-version None A version spec: 24, 22.17.1, lts/*, latest. Wins over node-version-file
node-version-file None .nvmrc, .node-version, .tool-versions, mise.toml or package.json
architecture The runner's x86, x64 and so on; ignored with a warning unless node-version is set
check-latest false Look for a newer version than the cached one that still satisfies the spec
registry-url None Writes an .npmrc whose auth line reads NODE_AUTH_TOKEN
scope None; the repository owner for npm.pkg.github.com Scope for a scoped registry
token github.token on github.com Downloads Node builds from actions/node-versions
cache None npm, yarn or pnpm
package-manager-cache true Set false to stop automatic npm caching
cache-dependency-path None Lockfile path, glob or list used for the key
mirror None Alternative source for Node binaries
mirror-token None Sent as the Authorization header to the mirror

Inputs and outputs from actions/setup-node action.yml at tag v7.1.0 (commit 949feb2413d6458794dcd2491c4babbbce0c15c1), read 2026-11-12.

node-version or node-version-file

Set one. If you set both, the action warns "Both node-version and node-version-file inputs are specified, only node-version will be used" and ignores the file. With neither, it uses whatever Node is already on PATH, which changes when the runner image does.

A relative node-version-file is resolved against GITHUB_WORKSPACE, not the step's working directory, and v7.1.0 accepts an absolute path too, so a composite action can pass its own file. For package.json the action reads volta.node first, then a devEngines.runtime entry named node, then engines.node, then follows volta.extends. A file it can read but not parse logs a warning and carries on with no version, which lands you on the PATH Node again; the node version file page shows how to turn that into a failure.

Resolution checks the runner's tool cache for a semver match first, then downloads from actions/node-versions, then from nodejs.org. check-latest: true skips the cached answer when a newer match exists, at the price of a download.

Built-in caching, and when it runs

Three questions decide it. If cache is set, the action caches that package manager, which must already be installed: cache: pnpm before pnpm is on PATH fails the step. If not, and package-manager-cache is left at true, it caches npm only when package.json names npm in devEngines.packageManager or the top-level packageManager field. Otherwise nothing is cached.

What it saves is the package manager's global cache directory, never node_modules, so npm ci still runs and still builds the tree. The key is node-cache-<platform>-<arch>-<manager>-<hash>, built from your lockfile, and the restore is exact: change one dependency and the next run starts cold. The only fallback is for Yarn Berry projects whose dependencies are all managed locally, which may restore an older entry by prefix.

For several lockfiles, point cache-dependency-path at a glob or a list. For what the cache is worth on a real project, speeding up npm install has measured numbers.

steps:
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
      persist-credentials: false
  - uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
    with:
      version: 10
  - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
    with:
      node-version: 24
      cache: pnpm
      cache-dependency-path: |
        apps/web/pnpm-lock.yaml
        apps/api/pnpm-lock.yaml
Enter fullscreen mode Exit fullscreen mode

Cache rules from src/main.ts, src/cache-restore.ts and src/cache-utils.ts at setup-node v7.1.0, read 2026-11-12.

Caching rules moved twice in two months

The caching rules moved twice in two months. v5 turned caching on for any packageManager field, and projects that declared pnpm and installed it later with corepack failed in Set up Node.js before corepack ran (issue 1357). v6 narrowed automatic caching to npm.

Version Released What changed What to do
v4.0.0 2023-10-23 Runtime moved from node16 to node20 Nothing on hosted runners
v5.0.0 2025-09-04 Runtime moved to node24; caching on by default when packageManager is set Runner v2.327.1 or later on self-hosted machines
v6.0.0 2025-10-14 Automatic caching limited to npm; always-auth input removed Add cache: pnpm or cache: yarn explicitly; delete always-auth
v7.0.0 2026-07-14 Placeholder NODE_AUTH_TOKEN removed; cache-primary-key and cache-matched-key outputs; ESM internals Set NODE_AUTH_TOKEN yourself where a registry needs it
v7.1.0 2026-10-08 mise.toml support; absolute node-version-file paths; version validation and manifest fetch retry Nothing

From the actions/setup-node release notes and the README "What's new in V7" and "Breaking changes" sections, read 2026-11-12.

registry-url, NODE_AUTH_TOKEN and publishing

action.yml says registry-url writes a project-level .npmrc. The v7.1.0 source writes it to $RUNNER_TEMP/.npmrc and exports NPM_CONFIG_USERCONFIG pointing at it, so your repository's own .npmrc is left alone. The auth line it writes is //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}: the token is read at publish time from the step's environment, so put NODE_AUTH_TOKEN on the step that publishes.

Until v7 the action exported a placeholder NODE_AUTH_TOKEN of XXXXX-XXXXX-XXXXX-XXXXX when you had not set one. v7 removed it, and the README warns that Yarn Classic and older npm may now fail with registry-url set and no token. npm trusted publishing needs no token at all: npm 11.5.1 or newer, id-token: write, and a trusted publisher on npm that matches the repository and workflow exactly, or the publish returns E404.

The setup-node docs set package-manager-cache: false in every publishing example, because a poisoned cache in a job that holds a publish credential can expose it. Do the same.

permissions:
  contents: read
  id-token: write
steps:
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
      persist-credentials: false
  - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
    with:
      node-version: 24
      registry-url: https://registry.npmjs.org
      package-manager-cache: false
  - run: npm ci
  - run: npm publish
Enter fullscreen mode Exit fullscreen mode

Missing files, missing lockfiles and missing pnpm

The first two have full pages. The rest are one-line fixes. Failures after the step, in the install itself, are a different family: npm ci lockfile out of sync and no pnpm version is specified are the common two.

Message Cause Fix
The specified node version file at: <path> does not exist The input, joined to GITHUB_WORKSPACE, names no file Node version file does not exist
Dependencies lock file is not found in <workspace>. Supported file patterns: package-lock.json,npm-shrinkwrap.json,yarn.lock cache is set and no lockfile is at the root Dependencies lock file is not found
Some specified paths were not resolved, unable to cache dependencies. cache-dependency-path matched nothing Fix the glob; it is relative to the workspace
Unable to find Node version '<spec>' for platform <os> and architecture <arch>. No published build matches the spec on that platform Check the spec and the architecture
Unable to locate executable file: pnpm. cache: pnpm ran before pnpm was installed Move the pnpm setup step above setup-node

FAQ

Does actions/setup-node cache node_modules?

No. It caches the package manager's global cache directory, ~/.npm for npm, keyed on a hash of the lockfile, so npm ci still runs and still builds node_modules. The setup-node docs give the reason: the global cache can be reused across Node versions, so a matrix of three versions can share one entry.

Why does setup-node v5 fail immediately when using pnpm?

v5 turned caching on whenever package.json had a packageManager field, so a project declaring pnpm made the action look for pnpm before any later step had installed it (issue 1357). v6 limited automatic caching to npm. Upgrade, or set package-manager-cache: false on v5.

Why does an npm OIDC publish fail after setup-node sets registry-url?

Up to v6 the action exported NODE_AUTH_TOKEN=XXXXX-XXXXX-XXXXX-XXXXX when you had not set one, and the issue asking to drop that default (issue 1440) describes having to blank it to get OIDC publishing working. v7.0.0 removed the placeholder. On v7, check the npm version is 11.5.1 or newer and that the trusted publisher matches the workflow file.

Why can GITHUB_TOKEN not install private packages from another repository?

Because a package is only readable by the workflows its settings allow. The setup-node docs point to Manage Actions access in the package's settings, where you add the repositories whose workflows may read it; after that NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} works for packages in the same organization.

References

Top comments (0)