Skip to content

Mirror Remote — The Second Git Host

Where every managed repo publishes its second copy, and how that target is declared rather than hand-configured (#1747). (Back to reference index.)

A single GitHub remote makes every git operation a single point of failure. On 2026-08-17, Git Operations were degraded for roughly four hours; for that window no clone could fetch and no commit could be published anywhere. The mirror remote closes that gap with the cheapest possible layer: a second bare repo on a fleet machine, reachable over Tailscale, wired into every clone from declared state.

It touches no forge API — this is pure git plumbing, which is why it works when the forge does not.

Per the Prime Directive, the mirror target lives in exactly one place (mirrors.json), reconciliation is idempotent, and every outcome is reported rather than assumed.

Declared state

mirrors.json in BOB_SOURCE owns git publishing topology — and only that. Which projects exist is still owned by projects.json; which machines exist by machines.json. Schema: schemas/mirrors.schema.json, validated by make check-schemas.

json
{
  "_meta": { "version": 1 },
  "defaults": {
    "host": "farm-01",
    "user": "farm",
    "root": "~/git-mirrors",
    "remote_name": "mirror",
    "enabled": true
  },
  "projects": [
    { "name": "vidz", "enabled": false, "reason": "large media working tree" }
  ]
}

defaults is what every active project in projects.json inherits. The projects array is exceptions only — never restate a project that already matches the defaults, or the manifest becomes a second project registry that can drift from the first.

FieldMeaning
hostmachines.json id of the mirror host. Resolved to its Tailscale name for SSH and for the URL.
userSSH user on that host. Omit to let ~/.ssh/config decide.
rootDirectory holding the bare repos. Absolute, or ~/-prefixed for the SSH user's home.
remote_nameThe local git remote name wired into every clone. Conventionally mirror — never origin.
enabledfalse excludes the project. Always pair with reason.

The mirror URL is derived, never declared: <user>@<tailscale-name>:<root>/<project>.git. Renaming a host in machines.json therefore cannot leave a stale URL behind in a second file.

Commands

The entrypoint is bob-mirror (engine: ~/.claude/scripts/fleet/mirror.js). With no project argument and no --all, it operates on the project containing the current directory.

bash
bob-mirror resolve                      # effective config for this repo
bob-mirror reconcile --apply            # create the bare repo + wire the remote
bob-mirror publish --apply              # push to BOTH origin and the mirror
bob-mirror verify                       # prove the mirror is a usable copy
bob-mirror status --all                 # fleet-wide condition
CommandWhat it does
resolvePrints the effective config (defaults + override) and the derived URL. Read-only.
reconcileEnsures the bare repo exists on the mirror host and the local remote points at the declared URL.
publishPushes the checked-out branch (plus tags) to origin and the mirror.
verifyClones the mirror into a throwaway directory and proves it reproduces origin. Read-only.
statusReports declared / wired / reachable / in-sync per repo. Read-only.
OptionEffect
--allOperate on every project with an enabled mirror.
--applyActually mutate. reconcile and publish are dry-run without it.
--localreconcile: wire the local remote only, never contacting the mirror host.
--ref BRANCHpublish / verify: use this branch instead of the checked-out one.
--path DIROperate on this working copy instead of the one in projects.json.
--jsonMachine-readable output.
--no-probestatus: report declared state without touching the network.

Exit codes: 0 healthy · 1 operational failure (unreachable, push rejected, mirror stale) · 2 declaration error (bad manifest, unknown project or host).

Safety model

  • Dry-run by default. reconcile and publish print exactly what they would do and change nothing until --apply is passed.
  • Idempotent. An existing bare repo is left alone; an already-correct remote is reported, not rewritten. Running reconcile --apply twice is a no-op.
  • A retarget is never silent. If a clone's mirror remote points somewhere other than the declared URL, reconcile names what it replaced. A quietly repointed remote is how a repo ends up publishing where nobody declared.
  • Neither push hides the other. publish attempts origin and the mirror independently and reports both. A mirror failure is never masked by a healthy origin, and — the case this capability exists for — a healthy mirror push still happens when origin is unreachable, so the commit is not stranded.
  • Skips are not failures. A project that is not checked out on this machine, or is not a git repo, is reported as a visible skip with its reason. It does not poison a --all exit code, and it is never silently dropped.

Verification: status vs. verify

The two read-only commands answer different questions, and the difference matters during an outage.

status compares the refs the mirror advertises against the local tip. It is one ls-remote per repo — cheap enough to run for every project in a fleet snapshot — but it never asks the mirror for a single object. A bare repo whose objects were truncated would still read as in sync.

verify actually clones the mirror into a throwaway directory, checks out the branch, and compares that clone's tip against origin's — not against the local working copy, which may be ahead of or behind what was published. It then confirms the clone has a real working tree and passes a connectivity fsck. That is the question worth answering: could this copy stand in for origin?

$ bob-mirror verify --ref master
[ok] bigbrain (master) — mirror clone reproduces origin at 48804d2, 1372 files

A local commit you have not pushed does not fail verification — the reference point is origin, so verify measures the two published copies against each other, not your desk.

Fresh clones

A new clone materializes its mirror remote from declared state; there is no manual git remote add step. cdi does this during init:

bash
bob-mirror reconcile <project> --local --apply --path <clone-dir>

--local wires the local remote and deliberately does not contact the mirror host, so cdi cannot hang on a sleeping farm machine or an offline laptop. Creating the bare repo remains an explicit bob-mirror reconcile --apply. --path is what makes this correct for a clone that lives somewhere other than the path recorded in projects.json.

Reading a status report

MIRROR STATUS

PROJECT   WIRED  REACH  SYNC   LOCAL    MIRROR
--------  -----  -----  -----  -------  -------
bigbrain  yes    yes    yes    d059d2f  d059d2f
seebod    no     -      -      17cfe6e  -

  ! seebod: not reconciled — run: bob-mirror reconcile --apply

  - drminizer: not a git repo
ColumnMeaning
WIREDThe local remote exists and matches the declared URL.
REACHThe mirror answered an ls-remote. - means it was not probed (never reconciled).
SYNCThe mirror's tip equals the local branch tip. STALE means committed work exists on only one host.
LOCAL / MIRRORThe two tips being compared.

An unreconciled repo is reported as not reconciled rather than unreachable — the network is not at fault, and the fix is named in the message.

Fleet reporting

Mirror condition is carried in the fleet snapshot (scripts/fleet/audit.js), so the question "how much of my committed work exists on only one host?" is answerable for the whole fleet without visiting each machine.

"fleet" here means BoB's machine layermachines.json plus scripts/fleet/* — not the Fleet project (~/projects/fleet), the host-environment layer beneath BoB that composes these scripts rather than replacing them (vision: ~/projects/fleet/docs/vision.md). See the glossary.

Each project entry gains a mirror block (declared, wired, reachable, in_sync, both SHAs), and summary.mirror rolls them up. audit.js view renders it:

Mirror (second git host; UNMIRRORED = commits on one host only)
MACHINE          DECLARED   WIRED      UNMIRRORED   UNREACHABLE
macbook-pro-16   26         1/26       25!          0
FLEET            26         1/26       25!          0

UNMIRRORED is the number to read: repos whose commits live on exactly one host, and would therefore be unreachable during a forge outage. It covers three cases — never reconciled, stale, and unreachable — because all three mean the same thing for recovery.

Buckets are kept distinct rather than collapsed:

  • skipped — not cloned on this machine, or not a git repo. No commits to strand, so not a gap.
  • opted outenabled: false in the manifest. Reported with its reason, so a deliberate exclusion stays visible instead of vanishing.
  • unknown (?N) — wired but not probed this run. Not proven safe.

The probe cost is one ls-remote per wired repo; a clone that was never reconciled short-circuits before any network call, so an unmirrored fleet costs nothing to audit. Set BOB_FLEET_MIRROR_PROBE=0 to report declared state only, which moves wired repos into the unknown bucket rather than pretending they are in sync.

Recovering when github.com is down

The mirror is a complete, fetchable copy, so it can stand in for origin:

bash
# Keep committing and publishing while origin is unreachable
bob-mirror publish --apply          # origin fails loudly, the mirror succeeds

# Work from the mirror on another machine
git clone farm@farm-01:~/git-mirrors/bigbrain.git

# When origin returns, publish again — both remotes converge
bob-mirror publish --apply
  • Fleet audit — mirror condition is carried in the fleet snapshot alongside provision and readiness state.
  • Prime Directive — why the target is declared once and reconciled, rather than configured per clone.