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

> Since v6, actions/setup-node caches npm without being asked. Learn when that cache runs, how the version resolves, and how v7 changed publishing.

Source: https://latchkey.dev/learn/actions/setup-node  
Updated: 2026-11-12

`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`.

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.

```.github/workflows/ci.yml
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
```

| 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](/learn/github-actions/actions-setup-node-could-not-resolve-dist-node-version-file) 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](/learn/speed/speed-up-npm-install-in-github-actions) has measured numbers.

```.github/workflows/ci.yml
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
```

> 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.

```.github/workflows/publish.yml
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
```

## 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](/learn/failures/npm-ci-lockfile-out-of-sync) and [no pnpm version is specified](/learn/github-actions/pnpm-action-setup-no-version-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](/learn/github-actions/actions-setup-node-could-not-resolve-dist-node-version-file) |
| `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](/learn/github-actions/gha-setup-node-lock-file-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

- [actions/setup-node action.yml at v7.1.0 (read 2026-11-12)](https://github.com/actions/setup-node/blob/v7.1.0/action.yml)
- [actions/setup-node advanced usage: caching, version files, publishing](https://github.com/actions/setup-node/blob/v7.1.0/docs/advanced-usage.md)
- [actions/setup-node releases](https://github.com/actions/setup-node/releases)
- [actions/setup-node issue 1357: v5 fails immediately when using pnpm](https://github.com/actions/setup-node/issues/1357)
- [npm docs: trusted publishers](https://docs.npmjs.com/trusted-publishers)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
