Skip to content

github-backup(valid_mirror): a bare skeleton with zero refs passes as a verified mirror — clone_all records it ok and MANIFEST.json reports it 'mirrored' #3238

Open

Description

@dezren39

Problem

infra/github-backup/bin/ghb_common.py:34-38 — the one check every part of the github-backup system uses to decide "this mirror exists and is good":

def valid_mirror(path):
    if not os.path.isdir(path):
        return False
    r = run(["git", "-C", path, "rev-parse", "--is-bare-repository"])
    return r.returncode == 0 and r.stdout.strip() == "true"

--is-bare-repository is true from the moment git clone --mirror creates the destination skeleton — before any ref or object has been transferred. A clone killed in that window (the driver's own TIMEOUT = 5400 expiry in clone_all.py, an interrupt, a laptop sleep/reboot during the initial estate clone) leaves a bare directory with zero refs and zero objects that passes valid_mirror everywhere it is used:

  • clone_all.py:44-46 — clone_one returns {"status": "ok", "note": "already-present"} without cloning; clone_all.py:71 + the clone-status.jsonl ok_prev replay do the same on later runs, so the repo is counted in the DONE ok=… tally and never cloned.
  • manifest.py:40 — build() reports the empty shell as "status": "mirrored" in MANIFEST.json, the file the runbook calls the disaster-recovery source of truth, with head: null as the only tell.
  • fetch_mirrors.py:22 — the daily pass treats it as present rather than missing (a later successful fetch would heal the data, but until then every status surface claims coverage that does not exist).

This is the mirror image of #3233: there, a partial leftover that is not a bare repo wedges every rerun; here, a partial leftover that is a bare skeleton is certified as a verified mirror. Both come from the same root: nothing ever checks that a "valid" mirror can resolve a single ref.

Evidence (verified this run, main 357ec7f)

Laptop probe ~/git/fleet/probe-2026-10-31-laneA-validmirror/ (on the laptop), driving the real ghb_common.py / manifest.py (sha256 2eb427df… / a489798e…, identical in the sandbox clone, the laptop clone, and the probe copies):

  1. Skeleton control (git init --bare, the exact state a mirror clone creates before transport): valid_mirror(...) = True; git rev-parse --verify HEAD exits 1; 0 refs.
  2. Real killed clone: git clone --mirror file:///home/user/git/code (~1 GB .git), killed with SIGKILL the moment --is-bare-repository flipped to true: destination remains, valid_mirror(...) = True, rev-parse --verify HEAD exits 1, 0 refs, count-objects all zero.
  3. Manifest consequence: manifest.build() over a one-repo plan whose destination is the skeleton from (1) emits "status": "mirrored", head: null, and totals {'mirrored': 1, 'missing': 0}.
  4. Control: a complete mirror clone of a small repo — valid_mirror = True and rev-parse --verify HEAD exits 0 with the commit SHA, so a strengthened check keeps the good case green.

Why it matters

A backup system's worst failure is reporting coverage it does not have. Today an interrupted clone of a large repo — exactly the repos most likely to hit the 90-minute timeout or an interrupted laptop session — can be recorded as mirrored in clone-status.jsonl and in MANIFEST.json while holding no objects at all, and no pass will ever re-clone it: clone_all skips it as already-present, and reconcile.py only invokes the clone pass for mirrors it believes are missing. The gap is silent and permanent until a human inspects the tree by hand.

Acceptance criteria

Dedup (this run): title/keyword searches for valid_mirror, clone_all, "partial mirror clone interrupted" return only #3233 (filed minutes ago by the sibling lane — the non-bare leftover wedge; its evidence seeds a non-bare partial dir and its acceptance criteria assume validity fails there) and the umbrella #3215. No issue covers the bare-skeleton false positive or valid_mirror's bareness-only test. Related: #3219 (drain ack path), #3229 (inventory null owner).

Activity

  1. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/github-backupinfra/github-backup estate mirror system
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    effort/sSmall: expected under 2 hours of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Design-architect pass 29 (2026-10-31) — agent note: this issue is ADOPTED as a phase of design #3282 ("a mirror is what it can prove — one validity, outcome and alarm contract for the github-backup estate mirror", design doc: files/designs/code-github-backup-mirror-contract.md in the keep-the-four-repos-healthy goal workspace), linked as a sub-issue of #3282 and labelled source/design-architect. Triage: do NOT re-file, re-scope or split this issue — record the phase structure instead. Phase mapping: Phase 1 (validity & destinations) = #3238 + #3233; Phase 2 (recorded outcomes & the alarm) = #3250 + #3219 + #3270 + #3257; Phase 3 (durable state & plan retention) = #3240 + #3229 + closing audit #3283 (gated on Phases 1–2). The design records the pins that span the phases: validity = bare + HEAD resolves, with no emptiness exception inside the plan (build_plan excludes zero-commit repos); destinations are absent / valid / skeleton (remove + clone) / debris (quarantine to .partial-<timestamp> + clone); every fetch attempt ends in one recorded outcome shared by the daily loop and the drainer, and the drainer acks only on ok; the chronic alarm is a streak of 3 consecutive unavailable days (fail, timeout and missing all count); generated state is written atomically only through ghb_common helpers; reconcile retains a failed owner's previously planned entries. Where an origin's acceptance left a choice open (the streak-vs-window rule, missing-counts-vs-immediate, report-vs-retain, skeleton handling), the design doc records the decision — triage follows the design's pin, not the open alternative. Each origin keeps its own acceptance criteria and its already-queued triage packet — no new packet was created for it this pass.

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 7 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    work/assignedQueue worker lifecycle state
    on Nov 1, 2026
  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — state=picked_up; task_id=5684; worker=repo-worker-code; issue=#3238. Fresh claim with no prior progress. Next: revalidate issue state, hard-stop labels, and matching open PRs before creating/resuming the task worktree.

  8. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    queue/blockedQueue or user escalation state
    status/blockedQueue or user escalation state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 2, 2026
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — state=blocked; task_id=5684; worker=repo-worker-code; issue=#3238. Queue task blocked as packet_defect. The issue’s triage completion note routes the fix as task #5685, but this atomic claim is #5684 for the same issue/scope; the canonical queue row cannot be established safely. No source files were touched. Resume after task-ID/dedupe reconciliation. Empty worktree/branch created at /home/user/git/fleet/5684 and left untouched.

  10. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: reviewed (coverage loop-back)
    Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 5684 (small_fix, blocked, priority 50) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
    Found via: coverage loop-back 2026-11-03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/github-backupinfra/github-backup estate mirror systemeffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/blockedQueue or user escalation statequeue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/blockedQueue or user escalation statetask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions