Two Parallel Agents Edited the Same File; the Merge Dropped One Quietly
Two tickets landed the same morning, so I split them across two worktrees and let each agent run on its own branch. Both came back green: their own tests passed, their own review approved, their own CI ran clean. Both merged. A week later, the feature flag from ticket A was missing in production, even though ticket A's PR had shipped.
What actually merged
Each agent reached for the same shared file, the feature-flag registry: ticket A added a new flag, ticket B added a different flag and switched a default next to it. The code paths were separate, and so were the tests. When B merged on top of A's older base, Git kept B's hunks in that file and A's flag hunk was gone. It was not a conflict Git flagged loudly; B's version was authored against a stale copy, the rebase took B's region in the registry, and the suite never asserted the registry's full contents.
Each test only proved its own flag existed. Nobody asked whether the shared file still carried everyone's knobs.
Why green on every side hides it
Tests prove your own path, not the shared manifest. A unit test for ticket A asserts that A's flag behaves as expected in isolation. It does not read the registry after B merged and confirm A's entry is still present. The dropped change lives in the gap between two isolated suites.
Agents work in isolation and cannot see sibling branches. Each prompt described one ticket on one worktree; neither agent knew another branch touched the same registry, so neither claimed ownership, rebased against the current file, or flagged the overlap.
A clean merge means line numbers reconciled, not intent preserved. Git reports no conflict when hunks sit in different lines, even on the same file. A rebase onto a stale base can quietly take one branch's region wholesale. Git does not warn that this file already changed upstream.
The dropped surface has no integration assertion. Flags, router tables, shared type registries, and script blocks usually lack a test that enumerates the whole file, so a missing entry fails nowhere — it just vanishes after deploy.
The cheap habits that prevent the clobber
Own the file before editing it. When an agent reaches for a cross-cutting shared file — a flag registry, router table, shared types, package.json scripts, env schema, or the OpenAPI spec — check open sibling PRs that touch it. Claim the file for this branch or defer to the other branch instead of both editing blind.
Push per-feature config out of the shared manifest. A feature-owned config file loaded by a registry shrinks the collision surface to almost zero; each branch edits its own directory, and the shared file stays a loader, not a data dump.
Require a fresh rebase when a shared file moved upstream. A cheap CI check lists open PRs modifying the same file and flags a merge that overlaps a sibling hunk, rather than trusting a clean line-level merge.
Assert the shared file in one integration smoke test. Enumerate the expected entries in the registry and fail if one is missing; a dropped knob then fails at merge, not a week after deploy.
When the shared file is the contract
If the shared file is the OpenAPI spec or the generated shared types, two branches changing it independently produce clients that silently diverge. A dropped operation survives a green suite exactly the same way: each branch tests its own slice, and nobody reads the contract after merge. The cheap guard is to replay recorded request/response pairs against the local spec and a live sandbox before merge, so the missing operation shows as a red diff in front of the reviewer rather than as a gap in production traffic. That is the local-first loop we built into Powerduck: the spec stays the source of truth, and drift appears on screen instead of in a post-deploy check.
What I check now
- Did this PR edit a shared manifest, and is an open sibling PR touching the same file?
- Did the branch rebase onto a current base that already includes the sibling merge?
- Does any test assert the shared file's full contents, or only my own slice?
A clean merge means Git reconciled line numbers. It does not mean both branches' intent survived.