Programming Languages Bug Report

CI broke after moving past Node.js 24: corepack: command not found, and how we pin pnpm now

Our pipelines and Dockerfiles ran corepack enable before installing dependencies with pnpm. After we bumped the base image from Node.js 24 to 26, every build failed: corepack: command not found. Corepack is no longer bundled with Node.js from version 25. How we install it explicitly and keep versions pinned.

Lucas Moreau Lucas Moreau @lucasmoreau · September 24, 2026 · 7 min read · 43 Views
Banner: CI broke after moving past Node.js 24 with corepack command not found, pinning pnpm

On a Monday, every pipeline in our monorepo failed in the first step after checkout:

Step 4/12 : RUN corepack enable && pnpm install --frozen-lockfile
 ---> Running in 3f1a2b9c8d7e
/bin/sh: 1: corepack: not found
The command '/bin/sh -c corepack enable && pnpm install --frozen-lockfile' returned a non-zero code: 127

The GitHub Actions jobs failed similarly:

Run corepack enable
/home/runner/work/_temp/9b2c.sh: line 1: corepack: command not found
Error: Process completed with exit code 127.

What changed

On Friday, a platform pull request had bumped the base image and the CI Node.js version from 24, the LTS line we run in production, to 26, the newest release line, to try a new runtime feature. Its own checks were unit tests that run with a preinstalled pnpm, so they passed. Every pipeline that built an image or set up pnpm through Corepack broke after the merge.

docker run --rm node:24-bookworm-slim sh -c 'node -v; command -v corepack'
# v24.x
# /usr/local/bin/corepack

docker run --rm node:26-bookworm-slim sh -c 'node -v; command -v corepack || echo missing'
# v26.x
# missing

What we ruled out

  • A PATH problem. There was no corepack binary anywhere in the Node 26 image.
  • A broken image tag. The official images for 26 were built correctly; npm and npx were present.
  • Our packageManager field. It was still valid; nothing could read it without Corepack.

Root cause

The Node.js project decided to stop distributing Corepack with Node.js starting with version 25. Our build steps assumed corepack was always present; we had never run a release line after 24, so the bump to 26 was the first time it was missing.

What Corepack did

Corepack is a small tool that reads the packageManager field in package.json and provides the matching version of pnpm or Yarn on demand:

{
  "packageManager": "pnpm@10.15.0+sha512.0f2c..."
}

With corepack enable, running pnpm used exactly that version, downloaded and verified against the hash, without installing pnpm globally. It shipped with Node.js from 16.9 as an experimental feature and was never marked stable while bundled.

What changes from Node.js 25

Corepack remains available as a separate npm package, maintained independently. Node.js releases from 25 do not include it. Node.js 24 and earlier lines still bundle it, so the change appears exactly when a project moves to 25 or later.

Who is affected

Projects that run corepack enable in Dockerfiles, CI scripts or developer setup instructions, and any tooling that assumes corepack is on the PATH because Node.js is installed: editor integrations, task runners and monorepo tools that shell out to it. Projects using npm only, or installing pnpm or Yarn globally by other means, are not affected. Docker images based on Node.js 24 or earlier still contain Corepack.

Environment

Component Version / value
Node.js Bumped from 24 to 26 in one pull request
Package manager pnpm 10, version pinned in packageManager
CI GitHub Actions with actions/setup-node, Docker builds
Repository pnpm workspace monorepo, 14 packages

Immediate: back to the production major

We reverted the bump, pinning every build to the same Node.js major as production, from one source of truth:

FROM node:24.8-bookworm-slim
- uses: actions/setup-node@v4
  with:
    node-version-file: .nvmrc      # contains 24.8.0

That restored all builds within an hour. The process bug was that a toolchain change merged without running a full image build; pull requests that touch .nvmrc or Dockerfiles now run the complete pipeline.

Moving to Node.js 25 or later: install Corepack explicitly

When we do move to a release without bundled Corepack, the build installs it as a pinned package and keeps the packageManager workflow unchanged:

FROM node:26-bookworm-slim
RUN npm install --global corepack@0.34.0 \
 && corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
- uses: actions/setup-node@v4
  with:
    node-version-file: .nvmrc
- run: npm install --global corepack@0.34.0 && corepack enable
- run: pnpm install --frozen-lockfile

Pinning the Corepack version matters for the same reason as pinning anything else: a new Corepack release could change behaviour.

Alternative: install pnpm directly

pnpm can also be installed without Corepack, for example with the official setup action in GitHub Actions, which reads the version from packageManager:

- uses: pnpm/action-setup@v4      # uses the packageManager field when no version is given
- uses: actions/setup-node@v4
  with:
    node-version-file: .nvmrc
    cache: pnpm
- run: pnpm install --frozen-lockfile

In Docker, npm install --global pnpm@10.15.0 works too, but then the version is written in two places (Dockerfile and package.json), and they will drift. We prefer Corepack in images and the setup action in CI, both driven by packageManager.

Keep the packageManager field honest

The field stays the single source of truth. A CI check fails if the pnpm version running in the job does not match it:

expected=$(node -p "require('./package.json').packageManager.split('@')[1].split('+')[0]")
actual=$(pnpm --version)
[ "$expected" = "$actual" ] || { echo "pnpm $actual != packageManager $expected"; exit 1; }

Alternatives and trade-offs

  • Stay on Node.js 24 LTS. Reasonable for production; LTS lines are what we deploy anyway. The explicit Corepack install only matters when moving past 24.
  • npm only. Removes the problem but means migrating lockfiles and workspace configuration.

Developer machines

The same change affects laptops. Our setup guide used to say "run corepack enable once". For developers who install newer Node.js releases through a version manager, it now says to install Corepack globally first, and the repository's setup script checks for it and prints the exact command when it is missing, instead of failing later with an unclear error from pnpm.

How to test the fix

docker build --build-arg NODE_IMAGE=node:26-bookworm-slim -t ci-check .
docker run --rm ci-check sh -c 'corepack --version && pnpm --version'

We added a weekly scheduled job that builds with the newest Node.js release, so breaking changes appear in a non-blocking job before they reach the main pipeline.

Confirming in production

Builds returned to green on pinned Node.js 24. The weekly "next Node" job builds with Node.js 26 and the explicit Corepack install, and passes; the production move to 26 is planned for when it becomes LTS. Production images were never affected, since they were already pinned.

Communicating the change

We posted a short note to the engineering channel explaining the change in Node.js, the new setup command and the reason for pinning, because several teams maintain their own Dockerfiles outside the monorepo. Two of them found the same corepack enable line in their builds before it broke them.

Regression guard

  • A lint rule in CI rejects latest, current and other floating tags in Dockerfiles and workflow Node versions.
  • The packageManager consistency check above.
  • Renovate proposes Node.js version updates as pull requests, so major changes are deliberate.

Security implications

Floating tags change the toolchain without review, which is a supply chain risk as well as a reliability one. Pinned versions, a pinned Corepack, and the integrity hash in packageManager mean the package manager binary used in CI is exactly the one we reviewed. Corepack verifies downloads against that hash.

Prevention checklist

  • Do not assume corepack exists; from Node.js 25 it must be installed.
  • Pin Node.js versions in images and CI with a single source such as .nvmrc.
  • Keep packageManager as the source of truth for pnpm or Yarn versions.
  • Test upcoming Node.js releases in a scheduled non-blocking job.
  • Avoid floating tags anywhere in the build.

References

Add a label

#nodejs #ci-cd #corepack #pnpm #package-managers
Written by
Lucas Moreau @lucasmoreau

I build backend services in Go, Node.js and Python, mostly APIs and workers behind high-traffic products. Over the years I have become the person who gets the "it only fails under load" tickets. Those bugs are almost always about concurrency or resource limits: goroutines that never exit, a blocked event loop, a connection limit nobody knew existed. I write each one up with a small program that reproduces it, an explanation of the runtime behaviour behind it, the fix and the test or metric that proves it, so the next engineer does not need a load test to find it.

Advertisement

Comments

0

No comments yet. Be the first to comment!

Sign In to post a comment.