A docs commit that silently reverted 411 files — postmortem of a multi-session merge campaign
Landing four parallel AI-session work streams onto main in one night: a synthetic-base trick that collapsed 35 of 38 add/add conflicts to zero, and a ref-only branch advance that turned the next session's docs commit into a silent 411-file revert.
Sixty AI sessions share this repository. Tonight four of their work streams had to land on main
at once: the Work Desk V1 intake merge, a 52-commit provider-neutral runtime cutover, a UI Factory
lab with ~10k lines of uncommitted evolution, and a rules-based backend-factory lab. Two things from
that campaign are worth keeping.
The trick: synthetic bases collapse add/add conflicts
The runtime branch and main had both grown the same subsystem as sibling branches — 38 files
conflicted as add/add, where git sees no common ancestor version and dumps whole-file conflict
markers. But both histories did contain common content: each side had committed the same baseline
file before diverging. So for each path we mined both histories for the latest blob that appears
on both sides and fed it to git merge-file as a synthetic base:
ours = :2:$path # main's version
theirs = :3:$path # branch's version
base = latest blob present in BOTH histories for $path
git merge-file ours base theirsResult: 35 of 38 add/add conflicts merged with zero overlapping hunks. The two lines had made
textually disjoint changes relative to their shared baseline; git just couldn't see it without the
base. The remaining 16 files were resolved hunk-by-hunk with per-file evidence (which side's symbol
does the merged tree actually reference?), and the compile/test gates arbitrated the semantics:
cargo 982/0, desktop 587/587, full verify-local.sh green on the exact landed tree.
The accident: a ref-only advance is a time bomb
One session, correctly trying not to touch the dirty main checkout, advanced main with
git update-ref only. Files untouched — technically true, and it reported exactly that. But the
checkout's index still described the old tree, so git status began showing 442 phantom staged
deletions (every file the merge had added, apparently about to be deleted).
Then another session committed a small docs change on top of that skewed index. The commit message
said docs(wave): PaaS hash contract decision. Its real content, measured with
git diff --name-status: 308 deletions, 103 reverted files, 0 additions — plus the 3 intended
doc files (+16 lines). A one-file docs commit had silently reverted the entire intake merge, and its
author almost certainly never knew.
The fix kept history honest instead of rewriting it: a -s ours merge of the accident commit that
grafts back only the 3 intended doc files. The accident stays visible in the log; the tree keeps
everything; main fast-forwards to the corrected merge.
Lessons
- Never advance a checked-out branch's ref without syncing its checkout. "I didn't touch any files" is not safety — it arms the next committer.
- Mass staged deletions you didn't make are a stop sign. One
git statusread before committing would have prevented the revert. - Add/add conflicts between sibling branches usually have an invisible common base. Mine both histories for a shared blob before hand-resolving whole files.
- Commit messages describe intent, not content. Trust
git diff --name-status, not the subject line — especially in a repo where sixty agents commit.