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:

Exact symptoms and error messages Git halts the merge or rebase and injects standard conflict markers into pnpm-lock.yaml: Exact symptoms and error messages Git halts the merge or rebase and injects standard conflict markers into pnpm-lock.yaml:
Exact symptoms and error messages — the core idea of this section at a glance.
<<<<<<< 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.

A pnpm-lock.yaml conflict happens because two branches each regenerated the lockfile against different dependency changes, and git cannot merge the resulting large, structured file line by line. The lockfile encodes the entire resolved graph — every package's version, integrity hash, and edges — so two independent regenerations touch overlapping regions in ways git sees as conflicting. The conflict is not a corruption but a signal that both branches changed dependencies and their resolutions must be reconciled by re-solving, not by stitching text together.

The reason hand-merging is dangerous is that a lockfile is a consistent solution to a constraint problem, and manually combining two solutions can produce an inconsistent one — a graph that installs differently than either branch intended, or that no longer matches the merged package.json. Because the file is machine-generated and internally cross-referenced, a human editing conflict markers cannot reliably preserve that consistency, which is why the correct recovery regenerates the lockfile rather than resolving the markers by eye.

Resolution and configuration patch

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

Resolution and configuration patch Do not edit conflict markers. Resolution and configuration patch Do not edit conflict markers.
Resolution and configuration patch — the core idea of this section at a glance.
  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"
}

Take the merged manifest, discard the conflicted lockfile, and regenerate it with a fresh install:

# Accept the merged package.json files, then regenerate the lockfile
git checkout --theirs pnpm-lock.yaml   # or --ours; the content is replaced anyway
pnpm install                            # re-solves against the merged manifests
git add pnpm-lock.yaml
git commit

pnpm install reads the merged package.json files and produces a consistent lockfile for the combined dependency set, replacing whatever conflict markers were present. Verify with pnpm install --frozen-lockfile that the regenerated lockfile matches the manifests, and run the test suite to confirm the combined dependency set resolves and works.

CLI validation and debug commands

CLI validation and debug commands A clean grep, a zero-exit --frozen-lockfile, and a pnpm why that shows a single resolved version together confirm the co CLI validation and debug commands 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
CLI validation and debug commands — the core idea of this section at a glance.
# 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.
Prevention and CI/CD guardrails Prevention and CI/CD guardrails in production JavaScript package workflows. Prevention and CI/CD guardrails Prevention and CI/CD guardrails in production JavaScript package workflows.
Prevention and CI/CD guardrails — the core idea of this section at a glance.
  • Never resolve lockfile conflict markers by hand — regenerate the lockfile from the merged manifest.
  • Route dependency updates through dedicated, scheduled PRs so lockfile changes do not collide with feature work.
  • Rebase frequently so a long-lived branch does not accumulate a large lockfile divergence.
  • Verify the regenerated lockfile with a frozen install before committing the merge.

Why the regenerate-don't-merge rule holds

The rule to regenerate rather than merge a lockfile follows directly from what a lockfile is: the deterministic output of the manifests plus the resolver. Given the merged package.json files and a pinned pnpm version, there is exactly one correct lockfile, and running pnpm install produces it. A hand-merge, by contrast, tries to reconstruct that output by combining two partial views, which has no guarantee of matching what the resolver would produce and can silently encode a version that satisfies neither branch's actual requirements. Trusting the resolver to regenerate is both easier and more correct than trying to out-think it.

Regenerate, don't merge Merged manifests plus the pinned resolver yield one correct lockfile. merged manifests the input pinned pnpm install re-solve one correct lockfile consistent
The lockfile is the resolver's deterministic output — regenerate it rather than hand-merge.

This is also why pinning the pnpm version with packageManager and Corepack matters for conflict recovery specifically. If two developers regenerate the lockfile with different pnpm versions, the outputs can differ in ways that create spurious conflicts even when the dependency changes are compatible. With the version pinned, everyone's pnpm install produces the same lockfile for the same manifests, so a conflict genuinely reflects a dependency divergence to reconcile rather than a tooling difference. The combination — pin the resolver, regenerate on conflict, verify with a frozen install — turns lockfile conflicts from a recurring source of friction into a mechanical, reliable step.

Preventing lockfile conflicts before they happen

Most lockfile conflicts are avoidable by controlling when and how the lockfile changes. The single most effective practice is to route dependency updates through dedicated pull requests — an update bot's grouped, scheduled PRs — rather than letting them ride along on feature branches. When lockfile changes are isolated to their own PRs, they merge quickly and rarely collide with each other or with feature work, because feature branches touch source, not the lockfile. A feature branch that does need a new dependency should add it, regenerate, and rebase promptly so its lockfile change lands before it diverges far from main.

Prevent conflicts Isolate updates, pin the resolver, rebase often. updates in own PRs isolated changes pin resolver identical regeneration rebase often small divergence
Small, isolated lockfile changes keep conflicts rare and trivial to regenerate.

Frequent integration is the other half. A long-lived branch accumulates a large lockfile divergence that is more likely to conflict and harder to reconcile; rebasing regularly keeps each branch's lockfile close to main so any conflict is small and recent. Together with pinning the resolver, these practices mean the lockfile changes in small, isolated, well-understood increments rather than in large, overlapping ones — which is what keeps conflicts rare, and the ones that do occur trivial to regenerate away.

Configuring git to reduce lockfile conflict noise

Git offers a few settings that make lockfile conflicts less frequent and less noisy, worth configuring once for a team that hits them often. A .gitattributes entry can mark the lockfile with a merge strategy that prefers regeneration, and some teams set the lockfile to use git's union merge to reduce spurious textual conflicts — though the safest practice remains to regenerate rather than trust any automatic merge. More impactful is enabling git rerere (reuse recorded resolution), which remembers how you resolved a conflict and replays it, useful when rebasing a branch repeatedly against a moving main.

Reduce conflict noise gitattributes and rerere ease conflicts; pinning prevents them. gitattributes + rerere less friction pin resolver identical regen isolated update PRs fewer conflicts
Git settings ease the conflicts that occur; pinning the resolver reduces how many do.

The most effective configuration, though, is organizational rather than git-level: ensuring everyone regenerates the lockfile with the same pinned pnpm version so conflicts reflect genuine dependency divergence rather than tooling differences. A .gitattributes marking and rerere reduce the friction of the conflicts that do occur, but pinning the resolver with packageManager and Corepack, and routing dependency changes through isolated PRs, is what reduces how many occur in the first place. Combining the git-level noise reduction with the workflow-level prevention gives a team both fewer lockfile conflicts and a mechanical, low-stress way to resolve the ones that remain.

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.

How do I resolve a pnpm-lock.yaml merge conflict?

Don't merge it by hand. Keep the merged package.json files, replace the conflicted lockfile (git checkout --theirs pnpm-lock.yaml), and run pnpm install to re-solve against the merged manifests. Verify with pnpm install --frozen-lockfile and commit.

Why shouldn't I edit the lockfile conflict markers directly?

A lockfile is a consistent solution to a constraint problem; hand-combining two solutions can produce an inconsistent graph that installs differently than either branch intended. Regenerating with pnpm install produces the one correct lockfile for the merged manifests.

How do I avoid frequent lockfile conflicts?

Route dependency updates through dedicated, scheduled PRs so lockfile changes stay isolated, pin pnpm with packageManager so everyone regenerates identically, and rebase branches frequently so lockfile divergence stays small.

Can I configure git to auto-resolve lockfile conflicts?

You can reduce noise with a .gitattributes merge marking and git rerere, but never rely on an automatic textual merge for a lockfile — it can produce an inconsistent graph. The reliable resolution is always to regenerate the lockfile from the merged manifest with pnpm install.

Which side should I keep, --ours or --theirs, for the lockfile?

It does not matter — the content is replaced entirely by pnpm install, which re-solves against the merged manifests. Pick either to clear the conflict markers, then regenerate and commit the result.

Should the lockfile be committed to version control?

Always. The lockfile is what makes installs reproducible and what CI's frozen install verifies against. Committing and reviewing it alongside the manifest is required; a repo without a committed lockfile has no reproducibility guarantee.

Related

Lockfile Management Strategies