- Book: AI Agents Pocket Guide
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
The second branch stops at package-lock.json. Two agents worked in two worktrees: one added a validation library, the other a date library. Each ran an install and passed its tests, and the first branch merged cleanly.
Often the manifest conflicts too, because both agents added a line to the same dependencies block.
Parallel agents make this routine. Every task that touches dependencies rewrites the same generated file, and git can't merge a generated file line by line with any confidence. Six of the seven package managers below document a fix, most of them a single command, and the seventh needs a short manual routine. Each one links to the tool's own docs.
The rule that covers all seven
Fix the manifest by hand. Never hand-edit the lockfile. package.json, composer.json, go.mod and Cargo.toml are files you own, so you decide which lines both branches keep. The lockfile is output, and the package manager should write it.
When the fix below says "take one side first", know which side is which. In a merge into main, --ours is main, the branch you're on, and --theirs is the branch coming in. In a rebase git swaps the labels: the git-checkout docs say --ours then "gives the version from the branch the changes are rebased onto". So when you rebase your branch onto main, --ours is still main's copy. Taking the target branch's lockfile keeps every version that has already landed, and the install adds only what the incoming branch brought.

Two installs, one generated file: the lockfile is where parallel branches meet first.
JavaScript: npm, pnpm, Yarn and Bun
1. npm (package-lock.json). npm's docs say that since npm@5.7.0 you can resolve these conflicts "by manually fixing any package.json conflicts, and then running npm install [--package-lock-only] again". npm then writes a merged lock "that includes all the dependencies from both branches". The flag skips touching node_modules/.
# package.json fixed by hand, then: npm install --package-lock-only git add package.json package-lock.json To check the result, npm ci exits with an error if the lock doesn't match package.json, instead of updating it.
2. pnpm (pnpm-lock.yaml). pnpm's docs say it "can automatically resolve merge conflicts in pnpm-lock.yaml". Run pnpm install and commit. If both sides are valid lockfiles, pnpm merges them. If not, it fails with a parse error, so read the result before you commit.
pnpm install git add pnpm-lock.yaml 3. Yarn Berry (yarn.lock). Yarn detects conflict markers on install and logs YN0049 AUTOMERGE_REQUIRED. Its heuristic takes the lockfile from the pulled branch, adds your branch's entries and installs again. When that works you get YN0048 AUTOMERGE_SUCCESS. Two failure modes are worth knowing. Under --immutable (typical in CI) Yarn refuses to edit the file and reports YN0047, so run the install locally. If someone committed the lockfile with markers still in it, Yarn can't find the two versions to merge (YN0083); revert the lockfile to a working version and install again.
yarn install # without --immutable git add yarn.lock 4. Bun (bun.lock). Bun switched its default to the text bun.lock in v1.2, and the announcement gives merge conflicts as one reason: binary lockfiles made them "harder to resolve". Bun's lockfile docs don't describe automatic conflict resolution, so I wouldn't count on it. Take the target branch's copy and let the install add what the manifest now asks for.
git checkout --ours bun.lock # main's copy bun install git add bun.lock One caveat for this one: the incoming branch's new package gets resolved again, so it can land on a newer version than the one the agent tested with. Read the lockfile diff.
PHP, Go and Rust
5. Composer (composer.lock). If the only conflict is the content-hash line, Composer's guide says composer update --lock "may be enough to remove the conflict marker". The CLI reference describes --lock as overwriting the hash "without updating package versions". For real conflicts, the guide says to accept both files from the branch with the most changes, then re-run the other branch's changes as commands:
git checkout --ours composer.json composer.lock # more changes composer require vendor/package composer validate Use --theirs instead if the other side changed more, then require what the side you dropped had added. composer validate checks that the lock is up to date with composer.json. The guide warns that re-requiring a package can pick a newer version than the other branch had locked.
6. Go (go.sum). Resolve go.mod by hand: keep both require lines. For go.sum, keeping both sides' lines is safe, because the Go reference says the file "may contain hashes for multiple versions of a module". Then tidy, which "will add missing hashes and will remove unnecessary hashes from go.sum":
# go.mod fixed by hand, markers removed go mod tidy git add go.mod go.sum In CI, go mod tidy -diff changes nothing and exits non-zero if the files are not tidy.
7. Cargo (Cargo.lock). Cargo's guide says Cargo.lock "is maintained by Cargo and should not be manually edited". Fix Cargo.toml, take the target branch's lock, then update the workspace. The --workspace flag updates only your own crates, and other packages "are updated only if they don't already exist in the lockfile", so the incoming dependency gets added and everything already locked stays put.
git checkout --ours Cargo.lock # main's copy cargo update --workspace git add Cargo.toml Cargo.lock cargo build --locked in CI fails if Cargo would have to change the lock.
For teams that hit this daily: merge drivers and branch lockfiles
If you land five agent branches a day, a manual step per conflict adds up. Three options, from narrowest to broadest.
Union merge for go.sum. git's built-in union driver takes lines from both versions instead of writing conflict markers. That suits a file that is a set of hash lines. The git docs warn the lines can come out "in random order and the user should verify the result", so run go mod tidy after the merge.
# .gitattributes go.sum merge=union Branch lockfiles in pnpm. With gitBranchLockfile: true in pnpm-workspace.yaml, each branch writes its own pnpm-lock.<branch>.yaml, so branches never touch the same file. pnpm install --merge-git-branch-lockfiles folds them back into pnpm-lock.yaml and deletes the branch files, and mergeGitBranchLockfilesBranchPattern makes that happen on its own on branches such as main.
A custom driver. git lets you register any command as a merge driver in .git/config and map files to it in .gitattributes. The gitattributes docs define the contract: the command gets %O (ancestor), %A (current) and %B (other), writes the result into %A, and exits zero for a clean merge. npm's v6 docs point at npm-merge-driver for this, but that repository was archived in August 2021 and is read-only, so plan on maintaining your own if you go this way.
Land one branch at a time, and fix the lock on the branch
The habit that makes all seven fixes painless: land the first branch, then rebase the second onto the new main inside its own worktree, fix the lockfile there and run its tests before it merges. The conflict gets resolved on a branch where a broken install costs nothing, not on main.
cd ~/code/app-add-dates git rebase main # fix package.json by hand, then: npm install --package-lock-only git add package.json package-lock.json git rebase --continue That second-worktree step is where Hermes IDE fits. I build it, so weigh this with that in mind. Each task you start with ⌘N (Ctrl+Shift+N on Windows and Linux) runs on its own hermes/ branch in its own worktree, so each agent's install stays on its branch until you land it. The Review Desk (⌘G) shows each task's changes by turn or by file, with risk flags. Land then commits, opens a pull request or merges locally, into the branch the task started from, one task at a time, and it can be undone.

The Review Desk's Worktrees tab: one Hermes worktree per task, each with its hermes/ branch, task, age and disk size.
You can also make the lockfile part of "done". A worktree recipe in .hermes/worktree.toml takes done_when checks, the default checks for when a task is done. Put the strict install there, and the task isn't done until npm ci passes against the lockfile:
# .hermes/worktree.toml setup = ["npm ci"] done_when = ["npm ci", "npm test"] Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Anthropic, OpenAI or any agent vendor. Download it or read the code on GitHub.
The cheat-sheet
| Lockfile | After fixing the manifest | CI check |
|---|---|---|
package-lock.json | npm install --package-lock-only | npm ci |
pnpm-lock.yaml | pnpm install | Read the diff |
yarn.lock (Berry) | yarn install | yarn install --immutable |
bun.lock | --ours lock, then bun install | Read the diff |
composer.lock | composer update --lock or re-require | composer validate |
go.sum | go mod tidy | go mod tidy -diff |
Cargo.lock | --ours lock, cargo update --workspace | cargo build --locked |
--ours is main's copy whether you merge into main or rebase onto it. Use --theirs only when you merge main into your branch.
If this was useful
Lockfile conflicts are one small symptom of a bigger question: how you split work between agents so their output can be merged at all. My AI Agents Pocket Guide covers that side: multi-agent orchestration, the guardrails around what an agent may change, and how to keep autonomous work reviewable. If you run Hermes, the worktree recipe above is a one-file way to start.

