Resolving Lockfile Merge Conflicts
11 Mar 2026Never hand-edit a lockfile to resolve a merge conflict. The temptation is real — it’s just JSON or YAML, you can see the conflict markers, you can pick a version and delete the other side. Do this and you’ll produce a lockfile that doesn’t match what your package manager would actually generate, and the breakage shows up later, somewhere unrelated to the merge.
Why hand-editing breaks things
A lockfile isn’t just a list of versions — it’s a resolved dependency graph, including the exact versions of transitive dependencies (the packages your packages depend on) and the integrity hashes for each. Manually splicing two conflicting sections together can leave you with a graph that’s internally inconsistent: a package listed at a version whose transitive dependencies weren’t updated to match. npm install or yarn install won’t necessarily catch this — it might just install the inconsistent state you handed it.
The actual fix: regenerate, don’t merge
# accept either side's package.json, doesn't matter which for this step
git checkout --theirs package.json # or --ours, resolve package.json conflicts normally
# delete the conflicted lockfile entirely
rm package-lock.json
# regenerate it fresh from the resolved package.json
npm install
# stage the result
git add package.json package-lock.json
git commit
This works because the lockfile is derived, deterministic output — you’re not merging two edits to it, you’re throwing away both and generating a fresh one from the package.json you already resolved by hand. The package manager, not you, decides the transitive versions, and it decides them correctly because that’s the one job a lockfile generator has.
Per package manager
# npm
rm package-lock.json && npm install
# yarn
rm yarn.lock && yarn install
# pnpm
rm pnpm-lock.yaml && pnpm install
# bundler (Ruby)
git checkout --theirs Gemfile && bundle install # regenerates Gemfile.lock
# poetry (Python)
rm poetry.lock && poetry lock
Every one of these follows the same shape: resolve the human-authored manifest file conflict normally (it’s small enough to read and reason about), then regenerate the derived lockfile from scratch.
What to check before committing
# npm: does the lockfile match package.json with no drift?
npm ci # this fails loudly if lockfile and package.json disagree, unlike npm install
# then actually run the test suite — a syntactically valid lockfile can still
# resolve to a version with a real behavior change
npm test
npm ci (not npm install) is the check that matters here — it’s strict about lockfile/manifest agreement in a way install isn’t, and it’s exactly the command your CI pipeline should already be running.
Why this happens more on feature branches
Two branches both add or bump a dependency independently, and by the time they merge, both have touched the lockfile in overlapping regions — even if the actual package.json changes don’t conflict, the lockfile’s alphabetically-ordered, deeply nested structure means unrelated changes often land near each other in the diff. This is inherent to how lockfiles are formatted, not a sign you’re doing something wrong.
What to skip
Skip trying to write a smarter lockfile merge driver or a custom git merge strategy for this. rm and regenerate is faster than any tool you’d configure to do it “properly,” and it’s guaranteed correct because it’s the same code path that generates the lockfile on a clean install — there’s no clever alternative worth building here.