feat(checkpoint): push checkpoint refs to one destination, and say where - #1905
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 7b9968b. Configure here.
There was a problem hiding this comment.
Pull request overview
This PR changes how the git-refs checkpoint backend selects a push destination on remotes with multiple push URLs, ensuring checkpoint refs are pushed to one deterministic destination (the first push URL) so the ref-only push queue can reason about “pushed” as a single place. It also adds user-facing surfacing of the chosen destination (pre-push warning, entire enable, and entire doctor) and updates integration coverage for the new behavior.
Changes:
- Add
resolveRefsPushDestinationto target the first push URL only for multi-push-URL remotes (git-refs), while keeping single-URL remotes byte-identical to prior behavior. - Surface ambiguous checkpoint topologies via
entire enableandentire doctor, and warn during pre-push when push URLs are being skipped. - Add
gitremote.GetPushURLsand update integration tests to validate the new single-destination behavior and destination reporting.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/strategy/refs_push_destination.go | Introduces deterministic destination resolution for git-refs checkpoint ref pushes, including multi-push-URL handling and warning/logging. |
| cmd/entire/cli/strategy/manual_commit_push.go | Wires destination selection into the pre-push and manual push paths; updates progress/error messaging and gates the checkpoint-remote hint. |
| cmd/entire/cli/setup.go | Adds a checkpoint destination note to entire enable output when topology is ambiguous. |
| cmd/entire/cli/remote_topology.go | New helper to inspect remotes/push URLs and describe ambiguous checkpoint destinations for enable/doctor. |
| cmd/entire/cli/integration_test/multi_pushurl.go | Adds a test helper to configure an unreachable first push URL. |
| cmd/entire/cli/integration_test/multi_pushurl_test.go | Updates git-refs expectations to “first push URL only”, adds unreachable-first coverage, and validates doctor destination surfacing. |
| cmd/entire/cli/gitremote/gitremote.go | Adds GetPushURLs to enumerate push destinations as git will use them. |
| cmd/entire/cli/checkpoint/remote/util.go | Wraps gitremote.GetPushURLs for checkpoint/remote callers. |
| cmd/entire/cli/doctor.go | Adds a doctor check that reports checkpoint destination ambiguity. |
Suppressed comments (1)
cmd/entire/cli/strategy/manual_commit_push.go:314
- Same as the pre-push path: emitting
warnIgnoredPushURLsbefore checking whether the queue is non-empty can print warnings even when there is nothing to push (and can duplicate output across multiple hook invocations on multi-push-URL remotes).
dest := resolveRefsPushDestination(ctx, ps)
dest.warnIgnoredPushURLs(ctx, os.Stderr)
pushed, err = flushCheckpointRefsQueue(ctx, repo, dest)
Review feedback on #1905: "rejected" still implies the remote answered, but batchPushRefs fails on transport errors too (unreachable host, auth). Same mistake as the "diverged" wording it replaced, one step milder. Says "failed". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KZ9FZCHDJD1625C8XGTEAK6F
cbcee40 to
bed532b
Compare
The git-refs push-discovery queue records only a ref (`{"ref": …}`) with no
per-destination state, so "this ref is pushed" has to mean one place. Relying on
git's fan-out across a remote's several push URLs cannot provide that:
- one failing URL fails the whole `git push` invocation, so no ref unqueues even
though other URLs took it — a dead mirror wedges the queue indefinitely;
- and git's iteration is not even uniform. A ref REJECTION returns and git
carries on to later URLs, but a transport failure (missing repo, auth, bad
host) die()s — so an unreachable FIRST push URL means nothing is pushed
anywhere, while the same mirror in last position is harmless. Position
silently decided the outcome.
So when a remote carries more than one push URL, checkpoint refs now target its
first push URL directly and the rest are skipped. The recovery fetch uses the
same target, which also fixes a mismatch: fan-out reconciled the remote's FETCH
url while pushing to its pushurls, so with a pushurl set we reconciled a repo
nothing was pushed to.
A single push URL keeps the remote NAME as the target, so the overwhelmingly
common topology is byte-identical to before — remote-tracking refs still update,
output still says "origin", no URL-keyed promisor config appears.
The git-branch backend deliberately keeps git's fan-out: its v1 branch is a
single shared ref with no queue to keep coherent, and mirroring it to every push
URL is what users configure those remotes for. The two backends therefore differ
on multi-URL remotes; that is documented at the decision point rather than left
to be discovered.
Trade accepted: checkpoint refs live in exactly one repository, so cloning a
different mirror of the same code will not find them, and a rejecting first URL
no longer lets later URLs receive the refs. checkpoint_remote remains the way to
name a checkpoint repository explicitly.
Because that is invisible otherwise, it is now announced in three places:
pre-push warns on stderr naming the chosen URL and how many were skipped;
`entire enable` notes it; `entire doctor` reports it under "Checkpoint
destination: REVIEW". All three also cover the other ambiguous topology —
several remotes, where checkpoints follow whichever remote you push to while
resume/explain always read via origin. All are silent on an ordinary repo and
whenever checkpoint_remote already pins a destination.
Two message fixes on the way past:
- the batch-rejection retry claimed "Some checkpoint refs diverged" for every
failure, including an unreachable destination — sending users after the wrong
problem. It now says the push was rejected without asserting why.
- printCheckpointRemoteHint ("a checkpoint remote is configured in Entire
settings") fired for any URL target and would now fire for a URL we picked
ourselves. It is gated on an actual checkpoint_remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01KZ992TB3ZNJJR9H9CHG01JCR
Cleanups from a four-angle review (reuse, simplification, efficiency, altitude).
No intended behavior change except where noted.
Efficiency — the one that mattered:
- resolveRefsPushDestination (and its `git remote get-url --push --all` spawn)
ran before flushCheckpointRefsQueue's early returns, so EVERY push on the
git-refs default backend paid a fork/exec for a destination that goes unused
when nothing is queued — and multi-URL users got the "checkpoints go to one
repository" warning on no-op pushes. It now resolves after both early returns,
inside the existing push_checkpoint_refs perf span. Behavior change, and the
intended one: the warning no longer fires on a push with an empty queue.
- inspectRemoteTopology replaced `git remote` + one `get-url --push --all` per
remote (N+1 spawns) with a single `git remote -v`, which already applies git's
pushurl-replaces-url rule and lists push URLs in order. Also removes an
inconsistency where remote listing ran in repoRoot while the URL lookups
inherited process CWD.
Correctness of what we tell the user:
- inspectRemoteTopology treated a checkpoint_remote *present in settings* as a
pinned destination and went silent. But PushURL only honors it when it
resolves — owner mismatch, unparseable URL and non-derivable protocol all fall
back — so doctor stayed quiet while pushes really did go first-URL-only. It now
asks remote.PushURL per remote, the "ask the client, don't re-resolve" rule
CLAUDE.md documents for CoreOrigin().
- It also reported the first *sorted* remote that fans out, so a repo with a
fanning-out `backup` remote that only ever pushes to origin got warned about a
rule that never applied to it. Ambiguity is now per remote, and each is named.
- The git-branch explanation described the git-refs failure behavior ("retries on
the next push"). Corrected: those URLs are reported and left behind, and only
the fetch URL is ever reconciled.
- refsPushDestination.display was set from displayPushTarget, which maps ANY URL
to the literal words "checkpoint remote" — so a self-picked push URL claimed to
be a checkpoint remote while the struct's own checkpointRemote said false.
display is now a method that names a resolved URL by its redacted URL.
Reuse and simplification:
- "redact if URL-shaped, else pass the path through" existed in three copies
(two added by the previous commit, one pre-existing in redactGitArgs). Now one
gitremote.RedactURLOrPath, forwarded through the checkpoint/remote facade.
- refsPushDestination drops its `display` field (a method), and the resolver's
five branches collapse to three.
- checkCheckpointDestination and printCheckpointDestinationNote were the same
four-step body; one function takes the header. `indent` was speculative
generality (one value at both call sites) and is gone, as is the third
redundant ambiguity check.
- warnIgnoredPushURLs uses the package's injectable stderrWriter instead of an
io.Writer parameter both call sites hardcoded.
- Dropped the `pushTarget := dest.target` alias.
- Test helpers: one setPushURLs(remote, urls...) primitive under all three
push-URL helpers, with position as the parameter — AddUnreachableFirstPushURL
was a verbatim copy of the Second variant with the slice order flipped. The
three doctor subtests became a table.
Stale rationale the previous commit invalidated: both file headers still said the
CLI "has no per-URL control at all", true only for git-branch now. Both carve out
git-refs.
Also fixed while in the file: checkCodexHookTrust's doc comment has been detached
from its function since before this branch (it sat above checkClaudeCodeHookDrift);
moved down to the function it documents.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01KZ9CNB8FYGCX05XV4V0D8NNX
Review feedback on #1905: "rejected" still implies the remote answered, but batchPushRefs fails on transport errors too (unreachable host, auth). Same mistake as the "diverged" wording it replaced, one step milder. Says "failed". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KZ9FZCHDJD1625C8XGTEAK6F
af5efcc to
1210d38
Compare

https://entire.io/gh/entireio/cli/trails/979
Why refs need one destination
The push-discovery queue records only a ref —
{"ref": …}, no per-destination state — so "this ref is pushed" has to mean one place. git's fan-out across a remote's several push URLs can't provide that, in two distinct ways:die()s. Verified:So an unreachable mirror in first position meant nothing was pushed anywhere, while the same mirror last was harmless. Position silently decided the outcome.
What changes
When a remote carries more than one push URL, checkpoint refs target its first push URL directly and the rest are skipped. The recovery fetch uses the same target, which also fixes a real mismatch: fan-out reconciled the remote's fetch url while pushing to its pushurls, so with a
pushurlconfigured we were reconciling a repository nothing had been pushed to.A single push URL keeps the remote NAME as the target, so the overwhelmingly common topology is byte-identical to before — remote-tracking refs still update, output still says
origin, no URL-keyed promisor config appears. Only multi-URL remotes are affected.git-branch deliberately keeps fan-out. Its v1 branch is a single shared ref with no queue to keep coherent, and mirroring it to every push URL is what people configure those remotes for (guarded by
..._Branch_RepeatedPushesKeepBothURLsInSyncin #1897). The backends therefore differ on multi-URL remotes; that's documented at the decision point inrefs_push_destination.gorather than left to be discovered.Trade, accepted deliberately
checkpoint_remoteremains the way to name a checkpoint repository explicitly.Saying where — three surfaces
Since the choice is otherwise invisible:
entire enablenotes it (both the first-time setup path and re-enable);entire doctorreports it underCheckpoint destination: REVIEW.All three also cover the other ambiguous topology — several remotes, where checkpoints follow whichever remote you push to while
resume/explainalways read viaorigin, so checkpoints pushed elsewhere aren't found again. All are silent on an ordinary repo and whenevercheckpoint_remotealready pins a destination.Two message fixes on the way past
printCheckpointRemoteHint("a checkpoint remote is configured in Entire settings") fired for any URL target, and would now fire for a URL we picked ourselves. Gated on an actualcheckpoint_remote.RedactURLrenders a plain filesystem path as:///path/to/repo; display now passes paths through and only redacts URL-shaped values.Tests
Refs tests from #1897 change meaning, so the diff shows the behavior flip:
..._Refs_FanOutToBothPushURLs→..._Refs_GoesToFirstPushURLOnly: first URL receives them, second does not, and they unqueue — the property the whole rule exists to enable...._Refs_UnreachableSecondPushURL_RefsStayQueued→..._IsIgnored: a broken later mirror is now irrelevant; refs land and unqueue. Previously this topology wedged the queue forever...._Refs_UnreachableFirstPushURL_ReachesNothing: thedie()case, previously uncovered...._DestinationNoteSurfaces: doctor reports both ambiguous topologies and stays silent on an ordinary repo.mise run checkgreen (lint 0 issues, canary 59 + 4).Note for reviewers
gitremote.GetPushURLsis also added by #1898. Whichever lands first, the other rebases and the duplicate drops out — trivial either way.🤖 Generated with Claude Code
Note
Medium Risk
Changes pre-push checkpoint sync for multi-push-URL remotes (git-refs only); wrong first URL or user expecting mirrors could miss checkpoints until they set checkpoint_remote.
Overview
Git-refs checkpoint ref pushes no longer rely on git’s multi-URL fan-out. When a remote has more than one push URL, refs go to the first push URL only (single-URL remotes still push by remote name, unchanged). Recovery fetch uses the same target so push and reconcile stay aligned.
Adds
gitremote.GetPushURLs/remote.GetPushURLs,resolveRefsPushDestination, andremote_topologysoentire doctorandentire enableexplain ambiguous setups (multi-push-URL remotes and multiple remotes vs origin for reads). Pre-push stderr warns when extra push URLs are skipped;checkpoint_remotestill pins an explicit repo and suppresses those notes.Also fixes misleading “refs diverged” retry messaging and limits
printCheckpointRemoteHintto configuredcheckpoint_remote.Integration tests flip from fan-out expectations to first-URL-only behavior and add doctor coverage.
Reviewed by Cursor Bugbot for commit 7b9968b. Configure here.