Daeseon Yoo
Back to project
·Troubleshoot·3 min

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 theirs

Result: 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

  1. 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.
  2. Mass staged deletions you didn't make are a stop sign. One git status read before committing would have prevented the revert.
  3. Add/add conflicts between sibling branches usually have an invisible common base. Mine both histories for a shared blob before hand-resolving whole files.
  4. Commit messages describe intent, not content. Trust git diff --name-status, not the subject line — especially in a repo where sixty agents commit.