Fixing pnpm ERR_PNPM_OUTDATED_LOCKFILE in CI
pnpm installs with --frozen-lockfile by default whenever it detects a CI environment. If any workspace manifest disagrees with pnpm-lock.yaml, the install stops with ERR_PNPM_OUTDATED_LOCKFILE instead of quietly re-resolving. The error is precise — it names the manifest that changed — but the underlying cause can be anything from a forgotten install to a changed overrides block or a different pnpm version. This guide shows how to read the message, fix the lockfile properly, and stop the error recurring.
Exact symptoms and error messages
The standard form names the manifest pnpm could not reconcile:
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with <ROOT>/packages/ui/package.json
Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"
Failure reason:
specifiers in the lockfile ({"react":"^18.3.1","clsx":"^2.1.0"}) don't match specs in package ({"react":"^18.3.1","clsx":"^2.1.1","tailwind-merge":"^2.5.0"})
When the mismatch is in settings rather than dependencies, the reason line changes:
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH Cannot proceed with the frozen installation. The current "overrides" configuration doesn't match the value found in the lockfile
Update your lockfile using "pnpm install --no-frozen-lockfile"
And a lockfile written by a newer pnpm major can be rejected outright, or silently rewritten locally while CI fails:
WARN Ignoring not compatible lockfile at /repo/pnpm-lock.yaml
ERR_PNPM_NO_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is absent
Root cause analysis
For every workspace importer — the root and each package — pnpm-lock.yaml stores the exact specifiers it was resolved from. A frozen install compares those stored specifiers with the current package.json files, and compares lockfile-level settings (overrides, packageExtensions, patchedDependencies, catalogs, and a few resolution options) with the current configuration. Any difference means the lockfile no longer describes what the manifests ask for, and a frozen install is not allowed to change it. Why frozen installs matter is covered in Lockfile Management Strategies.
The usual triggers:
- A manifest change without an install — editing a range by hand, adding a dependency with another tool, or a bot that failed to update the lockfile.
- A settings change — editing
overridesin the rootpackage.jsonorpnpm-workspace.yaml, adding apatchedDependenciesentry, or changing a catalog version without reinstalling. - A new or moved workspace package. Adding a directory that matches the
packagesglobs creates a new importer the lockfile does not have; renaming one leaves a stale importer behind. - Different pnpm versions. pnpm 9 writes
lockfileVersion: '9.0'. An older pnpm cannot read it, and a newer pnpm may reformat it; either way the local and CI views diverge.
Resolution and configuration patch
1. Reinstall locally with the CI pnpm version
corepack enable # uses the version in package.json "packageManager"
pnpm --version # must match CI
pnpm install # updates only the affected importers
git diff --stat pnpm-lock.yaml
pnpm install --frozen-lockfile # prove it now passes
git add pnpm-lock.yaml && git commit -m "Update lockfile for packages/ui dependency changes"
A plain pnpm install keeps every locked version that still satisfies its specifier, so the diff should mention only the packages from the failure reason.
2. Settings changes
When the failure is ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, the lockfile needs to record the new settings. The same local pnpm install does that. Make settings changes in their own commit together with the regenerated lockfile, because the diff can be large: an override that pins semver changes every importer that depends on it.
# pnpm-workspace.yaml (pnpm 10 keeps these settings here)
packages:
- "apps/*"
- "packages/*"
overrides:
semver: "^7.6.3"
catalog:
react: ^18.3.1
3. Version alignment
Pin pnpm in the root manifest so developers, bots and CI all write the same lockfile format:
{
"packageManager": "pnpm@9.15.4"
}
In GitHub Actions, pnpm/action-setup reads this field when no version is passed. See Pinning the Package Manager with Corepack.
Do not disable frozen installs in CI
pnpm install --no-frozen-lockfile in CI makes the error disappear by resolving new versions inside the runner. The tested tree then differs from the reviewed lockfile, and the updated lockfile is thrown away at the end of the job. Keep CI frozen; fix the lockfile in the branch.
Worked example: a catalog bump that broke every pipeline
A platform team moves shared versions into a pnpm catalog and later bumps catalog.typescript from ^5.6.0 to ^5.7.2 in pnpm-workspace.yaml, committing only that file. Every pipeline fails with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH because the lockfile's recorded catalog no longer matches. The fix is the same local pnpm install, which updates the catalog snapshot and every importer using catalog: for TypeScript, followed by a commit of pnpm-lock.yaml. The team then adds a pre-commit hook that runs pnpm install --lockfile-only whenever package.json or pnpm-workspace.yaml is staged, so configuration and lockfile always travel together. Catalogs themselves are covered in Sharing Dependency Versions with pnpm Catalogs.
Edge cases: patches, injected packages and filtered installs
A few less common setups produce the same error for reasons that are not obvious from the message.
Patched dependencies. pnpm patch and pnpm patch-commit record a patchedDependencies entry and a hash of the patch file in the lockfile. Editing the .patch file afterwards changes the hash, and the next frozen install fails with a configuration mismatch even though no manifest changed. Always re-run pnpm install after touching a patch file, and commit the patch and lockfile together.
Filtered installs in CI. Pipelines that run pnpm install --filter @acme/api... to install only part of a workspace still validate the whole lockfile against the importers they touch. A stale importer for an unrelated package can therefore fail a job that never builds it. That is by design — the lockfile is a single artefact — but it surprises teams who expect filtered installs to be isolated. Keep the lockfile valid for every importer rather than trying to scope the check.
Injected workspace dependencies. With dependenciesMeta.*.injected or inject-workspace-packages=true, pnpm copies workspace packages instead of symlinking them, and records extra metadata in the lockfile. Changing a workspace package's own dependencies then invalidates the importers that inject it, so a change in packages/ui/package.json can surface as a mismatch reported against apps/web.
Environment-specific settings. Settings such as node-linker, dedupe-peer-dependents or auto-install-peers affect resolution and are recorded in the lockfile. If a developer sets one of them in a user-level ~/.npmrc, their lockfile differs from CI's. Keep resolution-affecting settings in the repository's .npmrc or pnpm-workspace.yaml, never in personal config.
Detecting drift before CI does
The cheapest place to catch the error is before a commit exists. Three layers work together. A pre-commit hook runs pnpm install --lockfile-only when manifests change, so the lockfile is regenerated on the developer's machine with the pinned pnpm version. A pull-request check runs pnpm install --frozen-lockfile --lockfile-only, which validates the lockfile without downloading packages and finishes in seconds even for large workspaces. And the dependency bot is configured to run pnpm install after every manifest change it makes, with the same pnpm version.
CLI validation and debug commands
# Validate without installing packages (fast CI check)
pnpm install --frozen-lockfile --lockfile-only
# Which pnpm wrote the lockfile, and which is running?
head -1 pnpm-lock.yaml
pnpm --version
# Show importer specifiers for one package in the lockfile
grep -n -A12 "^ packages/ui:" pnpm-lock.yaml
# Show effective settings that are recorded in the lockfile
pnpm config list | grep -Ei "override|hoist|catalog"
Prevention and CI/CD guardrails
- Pin pnpm with
packageManagerand let CI read it rather than hard-coding a version in workflow files. - Commit lockfile changes with every manifest or settings change, enforced by a pre-commit hook.
- Run a lockfile-only frozen check as an early, fast pull-request job.
- Make bots regenerate the lockfile and fail their pull requests if they cannot.
Frequently Asked Questions
Why does pnpm install work locally but fail in CI?
Locally, pnpm is not frozen by default, so it quietly updates the lockfile. CI is frozen by default. Run pnpm install --frozen-lockfile locally to reproduce the CI behaviour before pushing.
Is it safe to delete pnpm-lock.yaml and reinstall?
It fixes the error but re-resolves every dependency to the newest version allowed by its range, which is an unreviewed upgrade of the whole tree. Prefer a plain pnpm install, which changes only what the manifests require.
Does the error mean my dependencies are insecure?
No. It only means the lockfile and manifests disagree. Security issues are reported by pnpm audit, which is a separate check.
How do I update the lockfile without installing anything?
Run pnpm install --lockfile-only. It resolves and writes pnpm-lock.yaml without linking node_modules, which is fast and useful in bots, pre-commit hooks and containers that should not download packages.
Why does the error name a package I did not touch? Because a shared setting changed. Overrides, catalogs, patches and injected workspace packages affect several importers at once, and pnpm reports the first importer it finds that no longer matches. Look at the failure reason line rather than the package name to see which kind of change caused it.
Related
- Lockfile Management Strategies sets the overall policy for frozen installs.
- Fixing pnpm-lock.yaml Merge Conflicts repairs the lockfile after conflicting branches.
- Fixing 'npm ci' Lockfile Out of Sync Errors covers the npm version of this failure.
- Sharing Dependency Versions with pnpm Catalogs explains the catalog settings recorded in the lockfile.