docs(gitmirrors): a push mirror can delete on codeberg, and how delivery is read #641

Merged
binjovi-bot merged 1 commit from docs/push-mirrors-prune into trunk 2026-09-11 13:22:48 +00:00
Owner

2fc3c577 corrected zot-src's branchFilter and predicted the stale seanfarm branch on Codeberg would be "left alone rather than deleted". It was gone on the next push.

Nothing was lost — seanfarm sat at 97d35c01, exactly what Forgejo now calls trunk — but the mechanism in that commit message is wrong, and a mechanism written down wrong is worse than one not written down at all.

The obvious generalisation is false, so this does not make it

My first draft of this correction asserted that a push mirror prunes whatever its filter omits. This fleet refutes that outright:

repo observed 2026-09-11
zot-src filter main,seanfarmmain,trunk; Codeberg's seanfarm was gone afterwards
hermes the push tried to delete feat/email-assistant, a branch its filter never named
n8n 3083 branches live on Codeberg beside a main,trunk,master filter, untouched for months

So something narrower deletes — plausibly a branch the mirror once managed and no longer matches — but that is a guess, and the README says so and invites replacement rather than inventing a law to fit three data points.

What follows regardless, because two repositories already paid for it

  • Treat narrowing a branchFilter as destructive to the backup. Check the tip you drop is reachable from one you keep.

  • A deletion Codeberg refuses takes the whole backup down with it, because the push is atomic. sean/hermes had feat/email-assistant as its Codeberg default branch; the mirror tried to delete it, the pre-receive hook refused (a default branch cannot be deleted), and main, trunk and every tag were rejected alongside it.

    hermes is the one private repository in the fleet, so it is also the one whose backup gap was least likely to be noticed. It went 9 commits stale. Fixed by pointing Codeberg's default at main, after proving the dropped branch was a reachable ancestor of main (13 commits behind) and therefore lossless.

  • A ref cannot be both a file and a directory. sean/n8n could not create refs/heads/main because Codeberg carries refs/heads/main/handshake-tomi-feedback. Over HTTP/2 this surfaced only as RPC failed ... stream reset by server, which reads like a size or network fault and is not — http.version=HTTP/1.1 showed the real error.

None of it is visible from the tree

Every guard here reads files, and in each case the tree was self-consistent: zot-src's CR and its live Forgejo mirror agreed on main,seanfarm, and hermes was declared exactly as intended. What disagreed was the source repository or the destination.

tests/gitmirror-fleet.sh already says as much in its own output — "this checks declared state only; delivery proof uses exact remote refs" — so the README now names where delivery is actually read: git ls-remote against Codeberg, or Forgejo's last_error at GET /api/v1/repos/sean/<repo>/push_mirrors.

Documentation only; no manifest behaviour changes.

https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8

`2fc3c577` corrected `zot-src`'s `branchFilter` and predicted the stale `seanfarm` branch on Codeberg would be *"left alone rather than deleted"*. **It was gone on the next push.** Nothing was lost — `seanfarm` sat at `97d35c01`, exactly what Forgejo now calls `trunk` — but the mechanism in that commit message is wrong, and a mechanism written down wrong is worse than one not written down at all. ## The obvious generalisation is false, so this does not make it My first draft of this correction asserted that a push mirror prunes whatever its filter omits. **This fleet refutes that outright:** | repo | observed 2026-09-11 | |---|---| | `zot-src` | filter `main,seanfarm` → `main,trunk`; Codeberg's `seanfarm` was **gone** afterwards | | `hermes` | the push tried to **delete** `feat/email-assistant`, a branch its filter never named | | `n8n` | **3083** branches live on Codeberg beside a `main,trunk,master` filter, untouched for months | So something narrower deletes — plausibly a branch the mirror once managed and no longer matches — but that is a guess, and the README says so and invites replacement rather than inventing a law to fit three data points. ## What follows regardless, because two repositories already paid for it - **Treat narrowing a `branchFilter` as destructive to the backup.** Check the tip you drop is reachable from one you keep. - **A deletion Codeberg refuses takes the whole backup down with it**, because the push is atomic. `sean/hermes` had `feat/email-assistant` as its Codeberg **default** branch; the mirror tried to delete it, the pre-receive hook refused (a default branch cannot be deleted), and `main`, `trunk` and every tag were rejected alongside it. hermes is the one **private** repository in the fleet, so it is also the one whose backup gap was least likely to be noticed. It went 9 commits stale. Fixed by pointing Codeberg's default at `main`, after proving the dropped branch was a reachable ancestor of `main` (13 commits behind) and therefore lossless. - **A ref cannot be both a file and a directory.** `sean/n8n` could not create `refs/heads/main` because Codeberg carries `refs/heads/main/handshake-tomi-feedback`. Over HTTP/2 this surfaced only as `RPC failed ... stream reset by server`, which reads like a size or network fault and is not — `http.version=HTTP/1.1` showed the real error. ## None of it is visible from the tree Every guard here reads files, and in each case **the tree was self-consistent**: `zot-src`'s CR and its live Forgejo mirror agreed on `main,seanfarm`, and hermes was declared exactly as intended. What disagreed was the source repository or the destination. `tests/gitmirror-fleet.sh` already says as much in its own output — *"this checks declared state only; delivery proof uses exact remote refs"* — so the README now names where delivery is actually read: `git ls-remote` against Codeberg, or Forgejo's `last_error` at `GET /api/v1/repos/sean/<repo>/push_mirrors`. Documentation only; no manifest behaviour changes. https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8
docs(gitmirrors): a push mirror can delete on codeberg, and how delivery is read
All checks were successful
binjovi/ci Binjovi completed the frozen plan
48ef81f0c5
`2fc3c577` corrected zot-src's branchFilter and predicted the stale `seanfarm`
branch on codeberg would be "left alone rather than deleted". It was gone on the
next push. Nothing was lost -- `seanfarm` sat at 97d35c01, exactly what Forgejo
now calls `trunk` -- but the mechanism in that commit message is wrong, and a
mechanism written down wrong is worse than one not written down at all.

THE RULE IS NOT KNOWN, AND THIS SAYS SO. The obvious generalisation is that a
push mirror prunes whatever its filter omits. That is refuted by this fleet:
sean/n8n keeps 3083 branches on codeberg beside a `main,trunk,master` filter,
untouched for months. So something narrower deletes, and the README records the
three measurements rather than inventing a law to fit them.

WHAT FOLLOWS REGARDLESS, because two repositories already paid for it:

- Treat NARROWING a branchFilter as destructive to the backup. Check the tip you
  drop is reachable from one you keep.
- A deletion codeberg refuses takes the whole backup down with it, because the
  push is atomic. sean/hermes had `feat/email-assistant` as its codeberg DEFAULT
  branch; the mirror tried to delete it, the pre-receive hook refused -- a
  default branch cannot be deleted -- and main, trunk and every tag were rejected
  alongside it. hermes is the one private repository in the fleet, so it is also
  the one whose backup gap was least likely to be noticed. It went 9 commits
  stale. Fixed by pointing codeberg's default at main, after proving the dropped
  branch was a reachable ancestor of main and so lossless.
- A ref cannot be both a file and a directory. sean/n8n could not create
  `refs/heads/main` because codeberg carries `refs/heads/main/handshake-tomi-
  feedback`. Over HTTP/2 that surfaced only as `RPC failed ... stream reset by
  server`, which reads like a size or network fault and is not; HTTP/1.1 showed
  the real error.

NONE OF IT IS VISIBLE FROM THE TREE. Every guard here reads files, and in each
case the tree was self-consistent: zot-src's CR and its live mirror agreed, and
hermes was declared exactly as intended. What disagreed was the source repository
or the destination. tests/gitmirror-fleet.sh already says as much in its own
output, so the README now names where delivery is actually read: git ls-remote
against codeberg, or Forgejo's last_error at
GET /api/v1/repos/sean/<repo>/push_mirrors.

Claude-Session: https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8
binjovi-bot deleted branch docs/push-mirrors-prune 2026-09-11 13:22:48 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/seanfarm!641
No description provided.