Skip to content

replicate: create missing refs in batches when the target refuses the pack - #132

Merged
Soph merged 3 commits into
mainfrom
soph/replicate-batched-after-rejection
Oct 28, 2026
Merged

Soph merged 3 commits into
mainfrom
soph/replicate-batched-after-rejection

Conversation

@Soph

@Soph Soph commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

https://entire.com/gh/entireio/-/trails/2659/changes/git-sync/2

Follow-up to #130 (ENT-3617). After a bootstrap is cut short, replicate now creates the missing refs in batches instead of re-sending one oversized pack on every retry.

Problem

Replicate routes to batched bootstrap only when every desired ref is missing on the target (replicateBootstrapRoute). Once any ref exists, the run goes down plain replicate, which fetches one pack for everything missing and pushes it in one request. Plain replicate has no recovery from a target body limit: it fails with the 413, and the next run sends the same pack again.

#130 pushes the trunk first, so a bootstrap that times out leaves the trunk on the target. The next run then takes plain replicate with every other branch in one pack. On a large repo that pack is over the target's body limit, so the repository never finishes mirroring.

Reproduced with real git http-backend and a 1 MB body limit on the target, using the mirror-pipeline worker's policy (replicate, prune, tags): an empty target bootstraps fine, while a target holding only the trunk fails on every run with http 413 … request body too large.

Change

  • When replicate's push fails with a body-limit or push-deadline error, the source supports batched bootstrap, and the run creates refs, replicate re-reads the target's refs and creates the missing ones with batched bootstrap. Updates and deletes then go out as their own replicate push. The run reports relay mode bootstrap-batch with the new reason replicate-pack-rejected.
  • bootstrap.Params.RejectedPush starts bootstrap in batched mode, so it does not re-send the pack the target just refused. The one-shot path and this one share batchAfterPushError.
  • With no trunk among the refs to create, batched bootstrap now plans every branch in one commit-graph fetch, with the target's refs as haves. A branch whose history the target already holds is created without a pack. Without this, each branch got its own fetch and its own checkpointed push. Bootstrap resume markers stay out of these haves, so an interrupted branch still resumes instead of starting over.

Separate commit: stale gated tests

TestBootstrap_GitHTTPBackendBatchedBranch and …BatchedBranchResume fail on main. They assumed a run executes exactly the batches it planned, but the plan's per-commit estimate is below these tests' blob sizes, so pushes split at runtime (planned 2, ran 6) and the bootstrap still succeeds. The test:git-http-backend task matched neither test, so nobody noticed. The assertions now check what the tests were after, and the task runs every TestBootstrap_GitHTTPBackend* and TestReplicate_GitHTTPBackend* test.

Not covered

  • A run with an affected target still makes one full-size push before the fallback starts (a full timeout, for a deadline error).
  • Updates and deletes still go out in one replicate pack, so an update that is by itself over the target's limit fails as before.
  • The sync path has no such fallback; the mirror-pipeline uses replicate.

Tests

  • TestRun_IntegrationReplicateBatchesMissingRefsAfterTargetBodyLimit (runs in CI, in-process target with a 300 KB body limit) fails without the fallback.
  • TestReplicate_GitHTTPBackendResumeAfterTrunkLanded (gated) covers an empty target, a target holding the trunk, and one whose trunk is a commit behind.
  • Bootstrap unit tests cover planning against target refs, starting batched from a refused push, a refused push batching cannot fix, and resume markers left out of the haves.
  • mise run test:ci, mise run test:git-http-backend and mise run lint pass.

🤖 Generated with Claude Code


Note

Medium Risk
Changes core replicate and bootstrap relay paths for large mirrors and partial targets; behavior is heavily tested but affects production sync completion after interrupted bootstraps.

Overview
Fixes replicate getting stuck after a bootstrap times out once the trunk is on the target: plain replicate kept retrying one oversized pack on every run when the target hit a body limit or push deadline.

When that push fails with a batchable size/deadline error and the run still has ref creates, replicate re-reads the target, creates only still-missing refs via batched bootstrap, then runs a separate replicate push for remaining updates/deletes. The run reports relay reason replicate-pack-rejected.

Bootstrap changes support that path: RejectedPush skips resending the refused pack and enters batched mode via shared batchAfterPushError. If the trunk is already on the target, planning uses targetHaves (excluding resume markers) for one commit-graph fetch for all remaining branches; tips already reachable on the target are created without a pack.

Also tightens git-http-backend test expectations (runtime batch splits vs planned count) and extends the test:git-http-backend mise task to cover replicate resume scenarios.

Reviewed by Cursor Bugbot for commit e04187d. Configure here.

Soph and others added 2 commits October 28, 2026 13:22
TestBootstrap_GitHTTPBackendBatchedBranch and
TestBootstrap_GitHTTPBackendBatchedBranchResume assumed a run executes
exactly the batches it planned. The plan sizes checkpoints from an
estimate per commit. These tests commit blobs bigger than it, so pushes
split further against the budget as they go: planned 2, ran 6. The
bootstrap succeeds; only the assertions were wrong.

The fresh run now checks that it ran at least the planned batches. The
resume test compares against a full run to a second target, not the
plan's checkpoint count.

The test:git-http-backend task matched neither test, so nothing ran
them. It now runs every TestBootstrap_GitHTTPBackend test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M617CJPZ8BW66V7GZNWYTFRH
… pack

Replicate routes to batched bootstrap only when every desired ref is
missing on the target. A bootstrap cut short after the trunk landed
leaves the next run on plain replicate, which sends every missing branch
in one pack. A target with a request body limit refuses it, and every
retry sends the same pack again, so the repository never finishes
mirroring. Since ENT-3617 lands the trunk first, a timeout leads here.

When the target refuses replicate's pack for its size or a push
deadline, replicate re-reads the target's refs and creates the missing
ones with batched bootstrap. Updates and deletes then go out as their
own replicate push. The run reports relay reason
replicate-pack-rejected.

Bootstrap supports this in two ways:
- Params.RejectedPush starts it in batched mode, so it does not re-send
  the pack the target just refused.
- With no trunk among the refs to create, it plans every branch in one
  commit-graph fetch against the refs already on the target. A branch
  whose history the target holds is created without a pack. Resume
  markers stay out of the haves, so an interrupted branch still resumes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M617CW1E88HRSS3TXE1WT2EW

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e04187d. Configure here.

Comment thread internal/syncer/syncer.go Resolved
- With no trunk among the refs to create, the commit-graph fetch stops
  where the target's haves begin, and planning had no stop set. The
  first-parent walk ends at a missing parent, but the topological walk
  fails on one, so --bootstrap-strategy=topo could not plan. The
  parents the fetch named but did not send are now the stop set.
- The push of updates and deletes after the batched creates offered only
  the target refs from the start of the run as haves. An update onto a
  branch bootstrap just created re-sent that branch's history and could
  hit the same body limit. It now re-reads the target's refs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M618HAN7QH02SVEA33TMQYGQ
@Soph
Soph merged commit 72a4e6d into main Oct 28, 2026
5 checks passed
@Soph
Soph deleted the soph/replicate-batched-after-rejection branch October 28, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants