Summary
When cache: true is combined with a runtime (via the runtime input or devEngines.runtime), the runtime is downloaded on every run and can never be served from the cache, because the cache is restored after the runtime is installed.
It still gets written into the cached directory, so it inflates every cache upload while never being read back.
Version: v2.0.2 (84cb39b, current tip of main).
Where it happens
src/index.ts, runMain():
:24 const result = await installPnpm(inputs)
:30 runtime = await installRuntime(request, result.binDest) // downloads the runtime
:38 await restoreCache(inputs) // cache arrives only here
:41 pnpmInstall(inputs)
Why the runtime lands inside the cached path
src/install-pnpm/run.ts:43 exports PNPM_HOME = dest.
src/cache-restore/run.ts:41 caches whatever pnpm store path returns. On a runner with no
user-level pnpm config that resolves to $PNPM_HOME/store/v11.
src/install-runtime/index.ts:46 runs pnpm runtime set <name> <version> -g, which unpacks the
runtime into that same store.
Measured locally with pnpm 11.13.1:
$ PNPM_HOME=/tmp/t/home pnpm runtime set node 24 -g
global:
+ node 24.19.0
Done in 3.2s using pnpm v11.13.1
$ du -sh /tmp/t/home/*
0 /tmp/t/home/bin # only a symlink into global/ -> store/
32K /tmp/t/home/global
128M /tmp/t/home/store # the runtime, inside the cached path
The real binary is at store/v11/links/@/node/24.19.0/<hash>/node_modules/node/bin/node;
$PNPM_HOME/bin/node is just a symlink chain to it.
Impact
- The runtime download is paid on every run, even on a full primary-key cache hit.
- Every cache upload carries roughly an extra 128 MB that is never read back.
- On GitHub-hosted runners this is a regression versus
actions/setup-node, which resolves a
matching version from /opt/hostedtoolcache essentially instantly — the ubuntu-24.04 image
ships Node 22.23.1 and 24.18.0 pre-cached.
Suggested fix
Move await restoreCache(inputs) to just after installPnpm(inputs) and before the runtime block,
i.e. between the current lines 24 and 27.
pnpm is already resolvable at that point: addPath() in src/install-pnpm/run.ts:41-42 mutates
process.env.PATH for the current process, which is what lets restoreCache shell out to
pnpm store path today. So the move should be mechanical.
It does need to stay after installPnpm, because src/install-pnpm/run.ts:23 does
rm(dest, { recursive: true, force: true }) and the store lives under dest.
A design question that comes with it
Once the runtime is actually served from the cache, the cache key needs to account for it. The
primary key today is pnpm-cache-${RUNNER_OS}-${arch}-${lockfileHash}
(src/cache-restore/run.ts:17), which contains nothing about the runtime — so changing the
runtime input would restore a store that still holds the previous runtime. Including the resolved
runtime name and version in the key would avoid that.
Environment
pnpm/setup v2.0.2
ubuntu-latest (ubuntu-24.04, image 20260720.247)
- pnpm 11.13.1
Summary
When
cache: trueis combined with a runtime (via theruntimeinput ordevEngines.runtime), the runtime is downloaded on every run and can never be served from the cache, because the cache is restored after the runtime is installed.It still gets written into the cached directory, so it inflates every cache upload while never being read back.
Version: v2.0.2 (
84cb39b, current tip ofmain).Where it happens
src/index.ts,runMain():Why the runtime lands inside the cached path
src/install-pnpm/run.ts:43exportsPNPM_HOME = dest.src/cache-restore/run.ts:41caches whateverpnpm store pathreturns. On a runner with nouser-level pnpm config that resolves to
$PNPM_HOME/store/v11.src/install-runtime/index.ts:46runspnpm runtime set <name> <version> -g, which unpacks theruntime into that same store.
Measured locally with pnpm 11.13.1:
The real binary is at
store/v11/links/@/node/24.19.0/<hash>/node_modules/node/bin/node;$PNPM_HOME/bin/nodeis just a symlink chain to it.Impact
actions/setup-node, which resolves amatching version from
/opt/hostedtoolcacheessentially instantly — theubuntu-24.04imageships Node 22.23.1 and 24.18.0 pre-cached.
Suggested fix
Move
await restoreCache(inputs)to just afterinstallPnpm(inputs)and before the runtime block,i.e. between the current lines 24 and 27.
pnpmis already resolvable at that point:addPath()insrc/install-pnpm/run.ts:41-42mutatesprocess.env.PATHfor the current process, which is what letsrestoreCacheshell out topnpm store pathtoday. So the move should be mechanical.It does need to stay after
installPnpm, becausesrc/install-pnpm/run.ts:23doesrm(dest, { recursive: true, force: true })and the store lives underdest.A design question that comes with it
Once the runtime is actually served from the cache, the cache key needs to account for it. The
primary key today is
pnpm-cache-${RUNNER_OS}-${arch}-${lockfileHash}(
src/cache-restore/run.ts:17), which contains nothing about the runtime — so changing theruntimeinput would restore a store that still holds the previous runtime. Including the resolvedruntime name and version in the key would avoid that.
Environment
pnpm/setupv2.0.2ubuntu-latest(ubuntu-24.04, image20260720.247)