CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(mirrors): sync would have silently destroyed local-only history #5494

MergedXSccantynz wants to mergefix/mirror-sync-would-destroy-local-only-historymainopened 20d ago
ccantynzcommented 20d ago

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 commits destroyed branches
vapron 531 8 local vs 790 upstream
davenroe 4
zoobicon 2

repo_mirrors being 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

runMirrorSync now stages upstream into refs/gluecron-mirror-check/* — outside refs/heads and refs/remotes, so staging cannot change anything a user, a clone, or --prune can 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:

  • Counts commits, not branches. A local branch whose tip is already upstream loses nothing when pruned, so it must not block the sync — conflating the two would leave every mirror permanently stuck.
  • Unreadable repo returns a positive sentinel and therefore refuses. "Cannot tell" must never resolve to "nothing to lose" — precisely the shape of the bug fixed on 2026-08-11.

Bonus: a latent CI hazard

Found while testing. autopilot-mirror-sync-failure.test.ts mocked ../lib/mirrors with only syncAllDue, and mock.module swaps the whole module for every importer in the process. It deleted getMirrorForRepo and friends for any suite sharing the run, so mirrors.test.ts died with Export named 'getMirrorForRepo' not found purely on file order. Both files are in CI (neither is excluded). The factory now spreads the real module.

Verification

  • Tests build real repos and run real git: in-sync, ahead-by-two, side branch upstream lacks, duplicate branch that must not block, and the unreadable case.
  • New suite fails against the pre-fix module; passes 7/7 after.
  • 42 pass across mirrors + autopilot + both mirror-sync suites together — which was 14 pass / 1 fail before the mock fix.
  • tsc --noEmit clean.

Still open after this

  • repo_mirrors has zero rows: the importer clones once (import.tsx:1012) and never creates one — upsertMirror has 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).
  • Outbound Gluecron→GitHub push remains #207, unbuilt.
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.

Analyze →
⮌ Merged

This pull request was merged into main.

c comment · e edit title · m merge · a approve · r request changes · ? shortcuts