fix(mirrors): sync would have silently destroyed local-only history #5494
ccantynzAI Reviewcommented 20d ago
AI Triage
(no summary)
Priority: medium Risk area: mixed
Suggested labels: (no label suggestions) Suggested reviewers: (no reviewer suggestions)
Suggestions only — nothing has been applied. The PR author stays in control.
gluecron[bot]🤖 botAI Reviewcommented 20d ago
AI review unavailable
The platform's AI balance is exhausted, so AI generation is temporarily unavailable. Nothing was lost. You can queue this as a repair for the internal agent from the repository's Health page, or try again once the balance is restored. The PR is otherwise unchanged.
Cross-repo impact
See what breaks downstream if this PR merges.
⮌ Merged
This pull request was merged into main.
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts
The footgun
A mirror sync fetches
+refs/heads/*:refs/heads/*with--prune— a force overwrite of every local branch plus deletion of any branch upstream lacks. On a repo holding the only copy of some work, that is unrecoverable loss, on a timer, with no confirmation step anywhere.Measured across the estate on 2026-08-12, this was not hypothetical. Enabling a mirror would have destroyed:
repo_mirrorsbeing empty is the only reason it never fired. The guard did not exist; the trigger was simply never pulled.This matters right now because the obvious fix for "mirroring is dead" is to add the missing rows — the very thing this investigation started from. Doing that first would have wiped 531 commits of vapron work on the following tick.
The guard
runMirrorSyncnow stages upstream intorefs/gluecron-mirror-check/*— outsiderefs/headsandrefs/remotes, so staging cannot change anything a user, a clone, or--prunecan see. It counts commits reachable from a local branch but from no staged upstream branch, and refuses when that count is non-zero, naming the number and the remedy.Divergence is treated as a failure, not as something to resolve by overwriting: a mirror is a copy, and a copy that deletes the original is not a copy. The refusal is recorded as a failed run, which since the 2026-08-11 fix the autopilot surfaces rather than swallows.
Two deliberate choices:
Bonus: a latent CI hazard
Found while testing.
autopilot-mirror-sync-failure.test.tsmocked../lib/mirrorswith onlysyncAllDue, andmock.moduleswaps the whole module for every importer in the process. It deletedgetMirrorForRepoand friends for any suite sharing the run, somirrors.test.tsdied withExport named 'getMirrorForRepo' not foundpurely on file order. Both files are in CI (neither is excluded). The factory now spreads the real module.Verification
tsc --noEmitclean.Still open after this
repo_mirrorshas zero rows: the importer clones once (import.tsx:1012) and never creates one —upsertMirrorhas exactly one caller, the manual form. Backfill is safe only for repos with 0 local-only commits (AlecRae.com, jarvis-platform verified clean; the three above are not).