Skip to content

pnpm install is a no-op when only pnpm-lock.yaml is changed (e.g., git checkout) — regression from v10 #12100

Description

@aqeelat

Summary

When the only change since the last install is to pnpm-lock.yaml (e.g., git checkout, git reset, or any external modification), pnpm install short-circuits with Already up to date and does not re-validate the lockfile against the manifests. This is a regression introduced in pnpm v11 by optimisticRepeatInstall (default-on).

Reproduction

In a workspace (single project also affected):

# Clean state
rm -rf node_modules pnpm-lock.yaml
pnpm install          # installs everything, creates node_modules/.pnpm-workspace-state-v1.json

# Now roll back only the lockfile — manifests stay put
git checkout HEAD~1 -- pnpm-lock.yaml
# (or equivalently: touch pnpm-lock.yaml, or any external lockfile edit)

pnpm install
# => "Already up to date"   ← BUG: lockfile was just changed, pnpm did not re-validate

Verified on pnpm v11.5.0 (published release) in ~/fathom/frontend/fathom-frontend-bench1 (27-project Angular monorepo, lockfileVersion 9.0).

Control test (correct behavior)

touch package.json
pnpm install
# => full re-install (manifest mtime triggers re-resolution)

So the optimistic check is correctly triggered by manifest changes, but not by lockfile-only changes.

Root cause

deps/status/src/checkDepsStatus.ts:263-271:

const modifiedProjects = allManifestStats.filter(
  ({ manifestStats }) =>
    manifestStats.mtime.valueOf() > workspaceState.lastValidatedTimestamp
)

if (modifiedProjects.length === 0) {
  logger.debug({ msg: 'No manifest files were modified since the last validation. Exiting check.' })
  return { upToDate: true, workspaceState }
}

When the lockfile is changed (e.g., via git checkout), its mtime is updated, but no manifest's mtime is touched. So modifiedProjects is empty, the function returns upToDate: true at line 270, and the lockfile content is never validated against the current manifests.

The lockfile-vs-current-lockfile equality check at line 292 only runs inside the modifiedProjects loop and never fires when no manifest was modified.

Why this is a regression from v10

In v10, optimisticRepeatInstall defaulted to false (see #11158). The same git checkout flow in v10 would trigger a normal install that re-validates the lockfile.

Impact

Affects any workflow where the lockfile can change without a corresponding manifest change:

  • git checkout / git reset / git stash pop of the lockfile
  • External tools that edit the lockfile (Renovate, Dependabot, custom scripts)
  • Manual lockfile edits (e.g., to fix a merge conflict)
  • Branch switches where the lockfile differs

In all of these, pnpm will silently keep a stale lockfile that may not satisfy the current manifests, leading to subtle bugs (e.g., wrong versions installed, missing transitive deps).

Workaround

pnpm install --config.optimistic-repeat-install=false

Or, temporarily:

touch package.json && pnpm install

Suggested fix

In the modifiedProjects.length === 0 branch, also check the wanted lockfile's mtime vs lastValidatedTimestamp. If the lockfile is newer, don't short-circuit — fall through to lockfile-content validation (which is already implemented at line 292+ and is fast since it only reads files, no network).

A more robust fix would be to store a hash of the lockfile content (or its mtime) in the workspace state and compare on the next run, so any lockfile change is detected regardless of how it happened.

Related issues


Written by an agent (opencode, minimax-m3-free).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions