Back to core workflows Fix dependency resolution Tune package metadata Jump to monorepo patterns

Fixing pnpm-lock.yaml Merge Conflicts

When two branches both add or bump dependencies and then meet at a merge, pnpm-lock.yaml conflicts almost every time — and Git's line-based merge produces a file that no longer parses as a valid dependency graph. This page walks through the exact recovery sequence that regenerates a clean lockfile, plus the repository configuration that stops the conflicts from blocking you in the first place.

Exact symptoms and error messages

Git halts the merge or rebase and injects standard conflict markers into pnpm-lock.yaml:

<<<<<<< HEAD
      /react@18.2.0:
        resolution: {integrity: sha512-...HEAD...}
=======
      /react@18.3.1:
        resolution: {integrity: sha512-...branch...}
>>>>>>> feature-branch

If you commit that file as-is, or hand-edit the markers, the next strict install fails with a parse or integrity error:

 ERR_PNPM_LOCKFILE_BREAKING_CHANGE  Lockfile is broken
 ERR_PNPM_UNEXPECTED_STORE  Unexpected store location
 ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE
   Cannot perform a frozen installation because the lockfile is out of sync

The frozen-install failure is the one that surfaces in CI, blocking the pipeline even after the merge "succeeds" locally.

Root cause analysis

pnpm-lock.yaml is a strict, deterministic YAML graph mapping exact package versions, integrity hashes, and peer-dependency resolutions. Git's three-way merge operates line by line and has no model of that graph, so when two branches change overlapping subtrees it interleaves their text rather than reconciling the resolution. Because pnpm uses a content-addressable store and strict peer-dependency enforcement, even a small dependency change can shift the topology of unrelated entries — which is why these conflicts are both frequent and impossible to resolve by editing markers. The right mental model comes from Lockfile Management Strategies: the lockfile is generated output, so you regenerate it rather than patch it.

Recover a conflicted pnpm lockfile by regenerating it Abort the conflicted merge, merge only the package.json files, regenerate the lockfile with pnpm install lockfile-only, then verify with a frozen install. conflicted lock git merge --abort merge manifests package.json only regenerate install --lockfile-only verify and commit --frozen-lockfile
Never edit the markers: abort, merge only the manifests, regenerate the lockfile, then prove it with a frozen install.

Resolution and configuration patch

Do not edit conflict markers. Follow this exact sequence to regenerate a valid graph and unblock the pipeline:

  1. Abort the conflicted merge state:
    git merge --abort
  2. Check out the up-to-date base branch:
    git checkout main
    git pull origin main
  3. Merge the feature branch, accepting only the manifest changes. The lockfile will conflict — discard it and keep the merged package.json files:
    git merge feature-branch
    git checkout --theirs pnpm-lock.yaml   # or --ours; the file is about to be regenerated anyway
  4. Regenerate the lockfile deterministically from the merged manifests:
    pnpm install --lockfile-only
  5. Verify graph integrity with a frozen install — this must exit 0:
    pnpm install --frozen-lockfile
  6. Commit the regenerated lockfile:
    git add pnpm-lock.yaml
    git commit -m "chore: resolve pnpm-lock.yaml merge conflict"

To make this automatic on future merges, register a merge driver so Git stops trying to text-merge the lockfile:

# .gitattributes
pnpm-lock.yaml merge=ours
# .git/config (run once, or distribute via a setup script)
[merge "ours"]
    driver = true

With merge=ours, Git keeps the current branch's lockfile on conflict; you then run pnpm install --lockfile-only in a post-merge hook to reconcile it against the merged manifests. Pin the pnpm version so every contributor regenerates the file identically:

{
  "packageManager": "pnpm@10.4.1"
}

CLI validation and debug commands

# Confirm no conflict markers remain anywhere in the lockfile
grep -nE '^(<<<<<<<|=======|>>>>>>>)' pnpm-lock.yaml && echo "MARKERS LEFT" || echo "clean"

# Prove the lockfile is internally consistent and in sync with the manifests
pnpm install --frozen-lockfile

# Trace why a specific version resolved the way it did
pnpm why react

# List the resolved workspace graph at top level
pnpm ls -r --depth=0

A clean grep, a zero-exit --frozen-lockfile, and a pnpm why that shows a single resolved version together confirm the conflict is genuinely resolved rather than papered over.

Prevention and CI/CD guardrails

  • Add the pnpm-lock.yaml merge=ours driver plus a post-merge pnpm install --lockfile-only hook so the lockfile is regenerated, never text-merged.
  • Pin pnpm with the packageManager field and Corepack so the lockfile serializes identically on every machine.
  • Run pnpm install --frozen-lockfile as the first CI step to catch any conflicted or stale lockfile before it reaches a build.
  • Add a pre-push grep for conflict markers (<<<<<<<) across the repo to block obviously broken lockfiles.
  • Keep dependency PRs small and merge them promptly to shrink the window where two branches diverge the same subtree.

Frequently Asked Questions

Is it safe to manually edit conflict markers inside pnpm-lock.yaml? No. Hand edits almost always break YAML structure or invalidate an integrity hash, and the next pnpm install --frozen-lockfile will fail. Always discard the conflicted file and regenerate it with pnpm's resolver.

Why does pnpm-lock.yaml change even when package.json is untouched? pnpm resolves transitive and peer dependencies dynamically, so refreshed registry metadata, a new patch version, or a workspace topology change will re-serialize parts of the lockfile to keep resolution exact and reproducible.

How can CI pipelines handle pnpm lockfile conflicts automatically? Configure the merge=ours driver in .gitattributes, then run a post-merge pnpm install --lockfile-only step followed by a --frozen-lockfile verification. The driver avoids the text merge; the regeneration reconciles the graph deterministically.

Should I commit pnpm-lock.yaml in a library package? Yes. The committed lockfile guarantees reproducible builds for contributors and CI. It is not published to consumers, but it remains essential for internal testing and workspace consistency.

Related

Lockfile Management Strategies