Skip to content

Runtime re-downloaded on every run: cache is restored after installRuntime #31

Description

@Ionaru

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions