fix(checkpoint): base checkpoint_remote ownership on origin, not the push target - #1898
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request adjusts how the CLI decides whether a configured checkpoint_remote should be honored, so that “is this setting mine (vs inherited from upstream)?” is determined from local provenance and the repo’s origin owner—rather than from the owner of the current push destination. This prevents misrouting checkpoint/session data when pushing to differently-owned remotes (e.g., backups or forks) and makes the behavior expressible for multi-push-URL remotes.
Changes:
- Treat
checkpoint_remoteas “inherited” unless it’s defined in.entire/settings.local.json(gitignored) ororigin’s owner matches the checkpoint repo owner. - Derive transport details from the actual push URL via
git remote get-url --push --all(instead of the fetch URL). - Expand unit test coverage and update docs/test plan to reflect the new ownership semantics and the local-only escape hatch.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates user-facing documentation to describe inherited-setting detection based on origin ownership and .entire/settings.local.json. |
| docs/testing/git-remote-test-plan.md | Updates test plan B5 to reflect the new “origin owner” inheritance rule and the two “must not fall back” cases. |
| cmd/entire/cli/strategy/checkpoint_remote.go | Updates strategy-layer documentation for the checkpoint remote resolution behavior. |
| cmd/entire/cli/settings/settings.go | Adds CheckpointRemoteIsLocalOnly to detect whether checkpoint_remote is present in .entire/settings.local.json. |
| cmd/entire/cli/gitremote/gitremote.go | Adds GetPushURLs to read push destinations from git config (remote.<name>.pushurl / repeated URLs). |
| cmd/entire/cli/checkpoint/remote/util.go | Moves the “inherited setting” decision to be based on .entire/settings.local.json + origin owner, and derives protocol from the push URL. |
| cmd/entire/cli/checkpoint/remote/util_test.go | Adds new TestPushURL cases and a helper to write .entire/settings.local.json. |
gtrrz-victor
previously approved these changes
Aug 7, 2026
…push target checkpoint_remote is normally committed in .entire/settings.json, so forking a project inherits it. e8b5898 added "fork detection" to stop a contributor's session data being pushed into the upstream project's checkpoint repo — a sound guard, implemented by comparing the PUSH REMOTE's owner to the checkpoint repo's owner. That conflates two different questions: "is this checkpoint_remote mine?" and "where is this particular push going?". Consequences: - Pushing to any differently-owned remote (a backup, a colleague's fork) disabled the user's own checkpoint_remote and pushed the checkpoints to that remote instead — the opposite of what the explicit setting asked for. - It cannot be expressed at all for a remote carrying several push URLs with different owners, since one boolean has to cover the whole set. Nothing pinned the intended reading: 16 of 18 PushURL cases use pushRemote:"origin", and the two "upstream" cases exercise a missing remote, not a differently-owned one — so "push remote owner" and "origin owner" were indistinguishable in the whole suite. Ask the ownership question directly instead, from two local signals (no network, so the pre-push path stays free of SSH prompts): 1. A checkpoint_remote in .entire/settings.local.json is gitignored and per-clone, so it cannot have been inherited — always ours. 2. Otherwise compare the checkpoint repo's owner against ORIGIN's owner: "am I working in a repo owned by whoever owns the checkpoint repo?" Both existing fork tests keep passing unchanged, because they were written with origin set to the fork — further evidence this is the reading that was meant. An origin whose owner cannot be determined still falls back, preserving today's behavior for local-path remotes. (1) is the escape hatch for a checkpoint repo legitimately owned by a different account or org than origin, which the previous check had no way to express. Also read the push remote's URL with `git remote get-url --push` when deriving transport. A remote's push destination is remote.<name>.pushurl when set, so the plain url could name a different repository than the one being pushed to. The inherited case now logs at Warn with the settings.local.json hint rather than Debug, so a user whose checkpoint_remote is being ignored can find out why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KZ74AZN68SXWB33E5DFRY7BJ
Entire trail finding (trail 971, medium): checkpointRemoteIsInherited treated a missing origin remote as automatically inherited, so it fell back and disabled checkpoint_remote. The predecessor compared the *push remote's* owner and never required a remote literally named origin, so a repo whose only remote is e.g. "upstream" — with an owner matching the configured checkpoint repo — silently lost checkpoint-remote routing under the new check. Origin still decides when it exists. It is only when there is no origin at all that the push remote serves as the identity, which is what the old code effectively did; in that topology there is no origin-vs-push-remote divergence to worry about, because the repo has exactly one identity. The check therefore moves after the push-remote read (needed for transport anyway) so that URL is available as the fallback identity. The reason strings now name which remote was consulted. Two test cases, both verified to fail against the previous logic (the first reported `enabled = false, want true`): - no origin + same-owner push remote -> checkpoint remote kept - no origin + differently-owned push remote -> still reads as inherited TestPushURL gains //nolint:maintidx: the score tracks the size of the case table, not branching logic, matching the existing convention in this repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KZ9H1VMB9X8J2R5YST5HX62R
Soph
force-pushed
the
soph/checkpoint-remote-fork-provenance
branch
from
August 7, 2026 11:16
0f814e8 to
940d905
Compare
gtrrz-victor
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/971
Background: what the fork check was for
checkpoint_remoteis normally committed in.entire/settings.json, so anyone who forks a project inherits it.e8b589835added "fork detection" to stop a contributor's session data being pushed into the upstream project's checkpoint repo — one they typically can't write to and shouldn't be writing to. Sound guard.It was implemented by comparing the push remote's owner to the checkpoint repo's owner.
The problem
That conflates two different questions: "is this
checkpoint_remotemine?" and "where is this particular push going?" Two consequences:checkpoint_remote— and the fallback then sends the checkpoints to that remote. A backup remote in another org, or a colleague's fork, receives your session transcripts instead of your configured checkpoint repo. Exactly the opposite of what the explicit setting asked for.remote.<name>.pushurlto repeat and pushes to all of them, so one boolean verdict has to cover a set of destinations that may have different owners. feat(checkpoint): push checkpoint refs to one destination, and say where #1905 has since removed the ambiguity on the git-refs backend by sending checkpoint refs to the first push URL only; git-branch still fans out to every one of them, so the problem is real there.Nothing in the suite pinned the intended reading: 16 of 18
TestPushURLcases usepushRemote: "origin", and the two"upstream"cases exercise a missing remote rather than a differently-owned one. "Push remote owner" and "origin owner" were indistinguishable everywhere.The fix
Ask the ownership question directly, from two local signals — no network, so the pre-push path stays free of SSH passphrase/security-key prompts:
checkpoint_remotein.entire/settings.local.jsonis gitignored and per-clone, so it cannot have been inherited by cloning or forking. Always ours.<fork>/appagainst an upstream-owned checkpoint repo, and mismatches.The push destination no longer enters the decision, which is what gives the multi-push-URL case a single answer.
Both existing fork tests pass unchanged — they were written with
originURLset to the fork (https://github.com/fork/app.git,entire://.../gh/fork/app), which is good evidence this is the reading that was intended all along.Signal 1 doubles as the escape hatch for a checkpoint repo legitimately owned by a different account or org than origin — a config the previous check had no way to express.
upstreammust not lose its configured checkpoint remote just because nothing is namedorigin. In that topology there is no origin-vs-push-remote divergence to worry about: the repo has exactly one identity. (Added in the second commit, from an Entire trail finding — see below.)What gets compared, case by case
checkpoint_remoteacme/appacme/checkpointsfork/appacme/checkpointssettings.local.jsonmyorg/checkpointssettings.jsonme/appmyorg/checkpointssettings.local.jsonbackupremote in another orgacme/appacme/checkpointsupstream=acme/appacme/appacme/checkpoints/tmp/repoacme/checkpointsThe property that matters: the last three rows no longer depend on where this particular push is headed, so the verdict is well-defined however many push URLs the remote carries.
Also in here
git remote get-url --pushwhen deriving transport. A remote's push destination isremote.<name>.pushurlwhen set, so the plainurlcan name a different repository than the one being pushed to. Latent until now becauseurl == pushurlin the common case. On a multi-URL remote this takes the first push URL — which post-feat(checkpoint): push checkpoint refs to one destination, and say where #1905 is not an arbitrary tiebreak but the URL checkpoint refs actually land in, so transport and (origin-less) ownership are now derived from the destination itself.settings.local.jsonhint, so a user whosecheckpoint_remoteis being ignored can find out why.Behavior changes
checkpoint_remoteinsettings.local.json, origin owner differspushurlin another repoSecond commit: Entire trail finding (trail 971, medium)
The review agent caught that the first commit regressed one topology: keying ownership on origin made a missing origin read as "inherited", so a repo whose only remote is
upstream— with an owner matching the checkpoint repo — silently lost checkpoint-remote routing, where the predecessor would have used it. Origin still decides when it exists; only its complete absence falls back to the push remote as identity. The check moved after the push-remote read (needed for transport anyway) to have that URL available, and the reason strings now name which remote was consulted.Finding resolved on the trail with the fixing commit.
Known interaction with #1893
#1893 adds a precedence tier that elects the current branch's push destination as the checkpoint sync remote, specifically to support the clone-upstream, add-your-fork topology (origin is a base repo you can't push; the branch tracks your fork). This PR's ownership rule assumes the opposite — that origin is yours.
Where they meet, the fork guard stops firing:
checkpoint_remote(committed)fork/appacme/checkpointsacme/appacme/checkpointsIn the second row a contributor's transcripts target the upstream project's checkpoint repo. The usual outcome is an auth failure and checkpoints never leaving the machine; where they do have write access, the session data lands upstream — the thing
e8b589835exists to prevent.This is the same class as the trail finding fixed in the second commit (keying on origin assumes origin is yours), and it only becomes reachable when #1893's tracking tier lands. Flagging it here; the resolution belongs to #1893, which is the PR that makes that topology first-class.
Not in scope
Divergence recovery across multiple push URLs. #1905 settled it for git-refs by picking one destination; on git-branch the original constraint stands (fast-forward-only push plus one local ref cannot satisfy N independently diverged remotes with cherry-pick reconciliation), and the executable specs merged with #1897 document the behavior. It needs its own design.
Test plan
TestPushURLcases, each verified to fail against the previous implementation: differently-owned push remote with matching origin;settings.local.jsonprovenance; transport derived from the push URL. Reverting just theGetPushURLcall makes the last one fail withhttps://…wheregit@…is expected.TestPushURLcases for the no-origin fallback, also verified against the previous logic (the first reported exactlyenabled = false, want true): no origin + same-owner push remote → honored; no origin + differently-owned push remote → still inherited.TestPushURLcases pass unchanged.mise run test:cigreen end to end (unit + integration-race+ canary 4/4),mise run lint0 issues. The multi-push-URL integration matrix feat(checkpoint): push checkpoint refs to one destination, and say where #1905 brought in passes unchanged, which is the check that matters here —remote.GetPushURLandresolveRefsPushDestinationnow read the same first push URL.docs/testing/git-remote-test-plan.md(row B5) updated to describe ownership by origin, including the two cases that must not fall back.🤖 Generated with Claude Code