Skip to content

git-remote-entire: ship the remote helper in the CLI, on a shared contexts.json login#1306

Merged
pjbgf merged 15 commits into
mainfrom
soph/git-helper
Jun 1, 2026
Merged

git-remote-entire: ship the remote helper in the CLI, on a shared contexts.json login#1306
pjbgf merged 15 commits into
mainfrom
soph/git-helper

Conversation

@Soph
Copy link
Copy Markdown
Collaborator

@Soph Soph commented May 30, 2026

https://entire.io/gh/entireio/cli/trails/460

Stacked on #1299 — review/merge that first; this PR's base is soph/entire-control-plane-client, so the diff here is only the helper + multi-context auth work.

What this does

Makes git clone entire://… work from the CLI by folding entiredb's git-remote-entire into this repo, and re-platforms the CLI's auth onto the shared kubectl-style contexts.json credential model so a single login authenticates both control-plane commands and clones — across any entitled cluster.

Shape of the change

1. Vendor the remote-helper protocol (internal/remotehelper/* + internal/entireclient/{discovery,httpclient}) — copied from entiredb with import rewrites; antithesis assertion dropped, version.GitRemoteHelperAgent swapped for a local stamp. Kept verbatim so the planned auth-go extraction stays a clean move.

2. Shared credential model (internal/entireclient/{tokenstore,contexts,clusterdiscovery,repocreds,httputil}) — adopts entiredb's keychain layout (entire-core:<issuer>/handle, token|expiry encoding) so a login in either CLI is visible to the other.

3. Multi-context auth

  • entire login dual-writes a contexts.json context (derived from the token's own claims) alongside the legacy entry.
  • A context-preferring ContextStore wires the control-plane readers (tokenmanager, LookupCurrentToken, auth status/list) at contexts.json, with legacy fallback + read-time migration so existing users aren't logged out.
  • entire auth contexts / entire auth use <ctx> to list/switch; logout clears the active context.

4. Dedicated git-remote-entire binary (cmd/git-remote-entire) — a lean main (~28 MB stripped vs the CLI's ~45 MB) that runs the helper loop directly. A real binary on PATH means no argv[0]/symlink/shim fragility, so Windows + Homebrew + scoop all work. Shipped via goreleaser (second build + cask binaries) and install.sh.

Notes / follow-ups (not in this PR)

  • auth-go extraction: the internal/entireclient/* packages are vendored copies of entiredb's; the intent is to promote them into auth-go and have both CLIs consume from there.
  • Retire entiredb's cmd/git-remote-entire once this ships.
  • goreleaser: the cask/scoop changes are standard fields but couldn't be validated offline (pro + license key) — worth a goreleaser check / snapshot before the next release.

Verification

Full unit (5884), integration, and e2e canary green; golangci-lint clean on the changed packages.


Note

High Risk
Touches authentication, credential storage, logout/revoke paths, and a new git transport surface; cluster discovery changes which login applies per host, so mis-binding or migration bugs could cause wrong-cluster auth or failed clones.

Overview
This PR makes entire:// remotes work from the Entire CLI by shipping a dedicated git-remote-entire binary (Goreleaser + Homebrew casks) and moving login onto a shared contexts.json + keychain model aligned with entiredb.

Auth: Login dual-writes a named context from JWT claims while keeping the legacy keyring entry. A ContextStore prefers the active context for token reads, STS, and auth status/list; logout/revoke-current also clear the active context. entire auth contexts and entire auth use list and switch contexts. Legacy logins can be migrated at read time for the helper.

Git remote: The helper resolves cluster auth via explicit bindings or /.well-known/entire-cluster.json (no silent reuse of current_context against the wrong cluster), exchanges login JWTs for repo-scoped pull/push tokens, and runs the smart-HTTP remote-helper protocol (connect/stateless fetch bridge, list, push options, replica discovery/cache).

Shared client libraries under internal/entireclient/ (contexts file locking, tokenstore, repocreds, cluster discovery, HTTP transport) and internal/remotehelper/ support the helper and future auth-go consolidation.

Reviewed by Cursor Bugbot for commit 1476f90. Configure here.

@Soph Soph requested a review from a team as a code owner May 30, 2026 17:40
Copilot AI review requested due to automatic review settings May 30, 2026 17:40
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 5 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, 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 1476f90. Configure here.

Comment thread cmd/entire/cli/auth/contexts.go
Comment thread cmd/entire/cli/auth.go
Comment thread cmd/git-remote-entire/main.go
Comment thread cmd/entire/cli/auth/context_store.go
Comment thread internal/entireclient/clusterdiscovery/resolve.go
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR folds the git-remote-entire remote helper into the CLI so git clone entire://… works out of the box, and migrates authentication toward a shared contexts.json + keychain credential model to enable single-login reuse across control-plane commands and git operations.

Changes:

  • Add a dedicated cmd/git-remote-entire binary plus remote-helper protocol/transport internals (smart-HTTP proxying, failover, replica caching, debug tooling).
  • Introduce shared auth/storage building blocks under internal/entireclient/* (token store, contexts, cluster discovery, STS exchange caching).
  • Update the CLI auth flows to dual-write contexts on login, prefer contexts on reads, and add entire auth contexts / entire auth use.

Reviewed changes

Copilot reviewed 74 out of 75 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
scripts/install.sh Installs git-remote-entire alongside entire from release archives.
scripts/entire-dev Runs go run on the whole cmd/entire package (not just main.go).
internal/remotehelper/transport/inforefs.go Implements info/refs cold/warm paths, redirect handling, and shared HTTP error shaping.
internal/remotehelper/transport/fault_test.go Adds failure-mode tests (truncation, ctx cancel, connection reset failover).
internal/remotehelper/replicas/replicas.go Adds persistent replica-cache adapter and per-invocation node config resolution.
internal/remotehelper/replicas/replicas_test.go Tests repo-path extraction and replica cache persist/load/invalidate behavior.
internal/remotehelper/name.go Defines shared helper binary name constant.
internal/remotehelper/httpdebug/roundtripper.go Adds debug-only HTTP request/response logging wrapper with redaction hooks.
internal/remotehelper/httpdebug/redact.go Implements header/URL/JSON token redaction and safe body previewing.
internal/remotehelper/gitproto/gitproto.go Adds pkt-line helpers for v0/v1/v2 smart-HTTP framing and request parsing.
internal/remotehelper/gitproto/fuzz_test.go Fuzz coverage for git protocol helpers to prevent panics/OOM.
internal/remotehelper/gitproto/consts_test.go Shared test constants for gitproto tests.
internal/remotehelper/gitproto/agent.go Logic to append helper agent identifiers to v0/v1/v2 requests.
internal/remotehelper/githelper/transport.go Defines the transport interface boundary for the helper loop.
internal/remotehelper/githelper/stateless.go Implements stateless-connect handling for protocol v2 upload-pack.
internal/remotehelper/githelper/stateless_test.go Tests stateless-connect behavior, agent amendment, and bundle-uri edge case.
internal/remotehelper/githelper/signal_test.go Pins cancellation/EOF behavior for the helper main loop.
internal/remotehelper/githelper/run.go Implements the git-remote-helper command loop (capabilities/list/connect/push).
internal/remotehelper/githelper/run_test.go Tests capabilities/options and connect-mode upload-pack flow.
internal/remotehelper/githelper/push.go Bridges git send-pack --stateless-rpc to receive-pack HTTP POSTs with auth/failover.
internal/remotehelper/githelper/options.go Tracks helper options and translates them into send-pack flags.
internal/remotehelper/githelper/options_test.go Tests option parsing and send-pack flag emission.
internal/remotehelper/githelper/list.go Implements list/list for-push parsing and symref/object-format emission.
internal/remotehelper/githelper/list_test.go Tests list output for symrefs, detached HEAD, unborn HEAD, and for-push.
internal/remotehelper/githelper/consts.go Centralizes service-name and option constants.
internal/remotehelper/githelper/consts_test.go Shared test constants for githelper tests.
internal/remotehelper/githelper/connect.go Implements connect-mode upload-pack negotiation and receive-pack relaying.
internal/remotehelper/githelper/connect_test.go Tests receive-pack connect mode, including push-size header behavior.
internal/remotehelper/githelper/agent.go Defines the helper’s default agent string (overridden at startup).
internal/remotehelper/debuglog/debuglog.go Adds ENTIRE_DEBUG-gated debug logger for the helper.
internal/remotehelper/debuglog/debuglog_test.go Tests debug logging gating and prefix behavior.
internal/entireclient/tokenstore/tokenstore.go Adds shared token store abstraction (keyring by default, file backend optional).
internal/entireclient/tokenstore/testing.go Test helper to force a file backend safely under package-level locking.
internal/entireclient/tokenstore/file.go Implements file-backed token store with flock + atomic writes.
internal/entireclient/tokenstore/file_test.go Tests file token store semantics and permissions.
internal/entireclient/tokenstore/expiry.go Adds `token
internal/entireclient/tokenstore/expiry_test.go Tests encoding/decoding and expiring-buffer logic.
internal/entireclient/repocreds/repocreds.go Adds cached RFC 8693 token exchange for repo-scoped JWTs.
internal/entireclient/repocreds/repocreds_test.go Tests caching/expiry/invalidation/concurrency behavior of repo-scoped tokens.
internal/entireclient/httputil/oauth.go Adds OAuth form-post helper with Basic-auth client credential lifting.
internal/entireclient/httputil/oauth_test.go Tests Basic-auth lifting + percent-encoding behavior for client creds.
internal/entireclient/httpclient/transport.go Centralizes HTTP transport construction and dial timeout config.
internal/entireclient/httpclient/transport_test.go Tests dial timeout env parsing behavior.
internal/entireclient/discovery/parse_replicas.go Adds parsing for X-Entire-Replicas and in-cluster host checks.
internal/entireclient/discovery/parse_replicas_test.go Tests replica parsing and HostInCluster behavior.
internal/entireclient/discovery/cache.go Adds on-disk replica/node cache with TTL and file locking.
internal/entireclient/discovery/cache_test.go Tests cache round-trip, expiry, misses, and invalidation.
internal/entireclient/clusterdiscovery/resolve.go Resolves auth contexts for a cluster via binding or /.well-known discovery.
internal/entireclient/clusterdiscovery/resolve_test.go Tests binding short-circuit, discovery+autobind, stale bindings, and error modes.
internal/entireclient/clusterdiscovery/discovery.go Implements /.well-known/entire-cluster.json fetch/parse and user hints.
internal/entireclient/clusterdiscovery/discovery_test.go Tests discovery behavior across status codes, transport errors, and nil debugf.
go.sum Adds/removes module sums related to new deps (e.g., flock).
go.mod Adds new dependency on github.com/gofrs/flock.
cmd/git-remote-entire/main.go Introduces the standalone remote helper binary wiring auth + transport + protocol loop.
cmd/git-remote-entire/main_test.go Tests request→git action classification for token scoping.
cmd/entire/cli/logout.go Extends logout to clear the active contexts.json context as well as legacy token.
cmd/entire/cli/logout_test.go Updates logout tests for the injected context-clear hook.
cmd/entire/cli/login.go Dual-writes contexts.json context on successful login (best-effort).
cmd/entire/cli/auth/store.go Switches “current token” lookup to prefer contexts.json via ContextStore.
cmd/entire/cli/auth/exchange.go Wires tokenmanager to use ContextStore for context-preferring reads.
cmd/entire/cli/auth/contexts.go Adds context recording, legacy migration, and token lookup by context.
cmd/entire/cli/auth/contexts_test.go Tests context record/migrate/token lookup + store preference + context removal/switching.
cmd/entire/cli/auth/context_store.go Implements context-preferring Store wrapper + context list/switch/remove helpers.
cmd/entire/cli/auth.go Adds new auth contexts / auth use commands and routes auth status/list through ContextStore.
cmd/entire/cli/auth_context.go Implements entire auth contexts and entire auth use.
cmd/entire/cli/auth_context_test.go Tests local listing output for contexts.
.goreleaser.yaml Adds a second build for git-remote-entire and ships it in Homebrew casks.

Comment thread internal/remotehelper/transport/inforefs.go
Comment thread internal/entireclient/clusterdiscovery/discovery.go Outdated
Comment thread internal/remotehelper/name.go Outdated
Comment thread internal/remotehelper/transport/inforefs.go
Comment thread internal/entireclient/clusterdiscovery/discovery.go Outdated
Comment thread internal/remotehelper/name.go Outdated
Comment thread internal/remotehelper/transport/inforefs.go
Comment thread internal/entireclient/clusterdiscovery/discovery.go Outdated
Comment thread internal/remotehelper/name.go Outdated
@Soph Soph force-pushed the soph/git-helper branch from 3e4c7f8 to 3e8f6d6 Compare May 31, 2026 18:28
@Soph Soph force-pushed the soph/entire-control-plane-client branch from 10b2341 to 213245b Compare June 1, 2026 10:14
@Soph Soph force-pushed the soph/git-helper branch from 3e8f6d6 to 29d8a91 Compare June 1, 2026 10:19
Base automatically changed from soph/entire-control-plane-client to main June 1, 2026 13:26
Soph and others added 15 commits June 1, 2026 15:59
Move the git remote-helper that powers `entire://` clones out of
entiredb and into the CLI so a single binary can serve both roles:
when invoked as `git-remote-entire` (via a shipped symlink), `main()`
branches into the helper protocol loop before any cobra/stdout
machinery runs, since git parses our stdout as a strict pkt-line
stream.

Copied verbatim (import paths rewritten):
  - internal/remotehelper/{githelper,gitproto,transport,replicas,
    debuglog,httpdebug} — the protocol loop, failover transport, and
    replica cache.
  - internal/entireclient/{discovery,httpclient} — the two
    self-contained client packages transport depends on.

Adaptations:
  - dropped the antithesis assertion from transport/proxy.go (no
    fuzzer in this repo).
  - replaced entire.io/version.GitRemoteHelperAgent() with a
    githelper.Agent var the entrypoint stamps from versioninfo.
  - wired auth to the CLI's existing single-context
    auth.RepoScopedToken. Full multi-context resolution (contexts.json
    + cluster discovery) is a follow-up; this stage gets a working
    `git clone entire://` for the current login.

Adds github.com/gofrs/flock (used by discovery's on-disk cache).

scripts/entire-dev now builds/runs the ./cmd/entire package instead of
cmd/entire/main.go alone — package main is now split across main.go and
remotehelper.go, and naming a single file left the dispatch out and
broke the local-dev git hooks.

This is the first stage of consolidating the helper into the CLI; the
shared credential packages will later be promoted to auth-go and
entiredb's cmd/git-remote-entire retired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 647feebd712d
The CLI's golangci config enables goconst (entiredb's did not), so the
vendored protocol code tripped it on repeated string literals. Extract
package-level constants:

  - githelper: serviceUploadPack/serviceReceivePack for the git
    smart-HTTP service names, optionValueTrue for git's boolean option
    literal, and test fixtures (testRefMain, testRefFeatureBranch,
    testHeadSHA).
  - gitproto: test fixtures (testRefMain, testRefFeatureBranch,
    testHeadSHA, testFakePackData).

No behaviour change; golangci-lint run (repo config, no --fix) reports
0 issues for the ported packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e0c9102fc8fb
Bring in the kubectl-style context store and its supporting packages so
the CLI can adopt entiredb's shared credential model (Stage 3 of folding
the git remote helper into the CLI):

  - internal/entireclient/tokenstore — OS-keyring storage under the
    entire-core:<issuer> / entire:<cluster> service scheme, keyed by
    handle, with the "token|expires_at" value encoding and sibling
    :refresh slots. This is the layout entiredb's CLIs already use, so
    once login is repointed at it a login from either CLI is visible to
    the other.
  - internal/entireclient/contexts — contexts.json (named contexts,
    cluster_contexts bindings, current_context) with atomic flock'd I/O.
  - internal/entireclient/clusterdiscovery — resolve a cluster host to a
    context via cluster_contexts or /.well-known/entire-cluster.json.
  - internal/entireclient/repocreds — RFC 8693 repo-scoped token cache.
  - internal/entireclient/httputil — just PostOAuthToken (the /oauth/token
    POST helper); the rest of entiredb's httputil (otel, middleware) is
    intentionally left out.

Adaptations for the CLI's stricter lint: wrap the PostOAuthToken error
(preserving *OAuthError via %w) and annotate the tokenstore backend
seam's interface returns. Copied verbatim otherwise; package tests pass
and golangci-lint reports 0 issues for the new packages.

Not yet wired into login/coreapi/the helper — that follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 6190bcfc6e80
…per cluster

Make `git clone entire://<any-cluster>/…` work from a single login by
moving the helper onto the shared contexts.json credential model, while
leaving the control-plane readers on the legacy entry untouched.

Login (cmd/entire/cli/auth.RecordLoginContext): after the device flow,
derive the issuer (core URL), handle, and expiry from the token's own
claims, store the token in the keyring under entire-core:<issuer>/handle
with the token|expiry encoding, and record/activate the matching context.
login.go calls this as a best-effort dual-write — the legacy
entire-cli/<authBaseURL> entry is still written, so the control plane is
unaffected and a failure here only warns.

Read-time migration (MigrateLegacyLoginContext): users who logged in
before this change get a context synthesized from their legacy keyring
token on first helper use, so no re-login is required. Idempotent.

Helper (cmd/entire/remotehelper.go): replace Stage 1's single-context
auth.RepoScopedToken with clusterdiscovery.ResolveContextForCluster (an
explicit cluster_contexts binding, else /.well-known discovery matched
against local contexts) feeding a repocreds cache that exchanges the
resolved context's login JWT for repo-scoped tokens per (repo, action).

Not done here (intentionally deferred): repointing the coreapi /
control-plane token readers at contexts.json. The legacy store keeps
them working; converging them is a low-risk follow-up, ideally with the
auth-go extraction.

Tests cover RecordLoginContext (happy path + missing issuer), the legacy
migration (synthesize + idempotent no-op), and LoginTokenForContext.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 932f4d26a055
Complete the contexts.json migration so a single login authenticates
every CLI command, not just clone, and let users manage which login is
active.

Readers (ContextStore): a context-preferring view over the legacy keyring
store — token reads resolve the active contexts.json context (its
entire-core:<issuer>/handle slot), falling back to the legacy
entire-cli/<authBaseURL> entry for pre-contexts logins. Wired into the
three control-plane readers:
  - LookupCurrentToken
  - the tokenmanager built in defaultManager (so coreapi's bearer +
    RepoScopedToken resolve via contexts)
  - auth status / list's "logged in?" check

This closes the asymmetry from the previous commit: a login created by
entiredb's CLIs (or `entire login`) now authenticates `entire repo
create`, org/project/grant, etc. — not only `git clone entire://`.

Logout now clears the active context (RemoveCurrentContext: drop its
keyring token + remove it from contexts.json, advancing current_context),
so the context-preferring readers no longer report a stale login. `auth
revoke --current` reuses the same path.

Context switching:
  - `entire auth contexts` — list stored contexts, marking the current.
  - `entire auth use <context>` — switch the active context.
cluster_contexts bindings remain auto-managed by clusterdiscovery on
clone, so no manual bind command is needed yet.

Tests cover ContextStore's read preference + fallback, RemoveCurrentContext
(clear + idempotent), SetCurrentContext (switch + unknown-context error),
and the contexts listing. Full unit suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 0e31e56b21a4
RemoveCurrentContext: collapse the separate Load + Modify into a single
locked Modify. The old form read contexts.json unlocked to grab the
keychain slot, then deleted under a second lock — a TOCTOU window where a
concurrent `auth use` could orphan the wrong keychain entry — plus a
redundant read and a re-find guard. Now it captures the slot and deletes
the context in one closure, then does the best-effort keychain delete
sequenced off what was actually removed.

Contexts: drop the unused named returns and the loadErr shadow of the
named err return.

No behavior change; auth package tests and golangci-lint (no --fix) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 1b6464f1632d
`git clone entire://…` needs a git-remote-entire helper on PATH. Rather
than the single entire binary dispatching on argv[0] (which forces a
symlink on Unix and a copy/shim on Windows, where shims can rewrite
argv[0] and break the dispatch), ship a small dedicated binary. A real
git-remote-entire[.exe] that git execs directly removes the argv[0]
dependency entirely, so symlink elevation, scoop shims, and per-package-
manager alias hacks all stop mattering.

  - cmd/git-remote-entire: lean main that runs the remote-helper loop
    directly, importing only the protocol + auth packages (no cobra
    tree). ~28MB stripped vs the full CLI's ~45MB — much cheaper than
    shipping entire twice. (Logic relocated from the earlier argv[0]
    path; behavior unchanged.)
  - cmd/entire: drop the argv[0] dispatch — each binary now has its own
    identity.
  - internal/remotehelper.BinaryName: the helper name, used by the lean
    main for its git agent string and usage text.
  - .goreleaser.yaml: second build for cmd/git-remote-entire (versioninfo
    stamp only, no telemetry) listed in both homebrew casks' binaries;
    scoop ships archive binaries too.
  - install.sh: install both binaries from the archive.

Full unit (5939), integration (352), e2e canary (59+4) green; lint clean
(no --fix) on changed packages; shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 6ded54a6ff1d
…text

Address three review findings in the multi-context auth flow:

1. Same-core accounts no longer collapse. RecordLoginContext keyed every
   context name on the core host, so a second login to the same core (a
   different handle) overwrote the first via Upsert. Contexts are now keyed
   by identity (core URL + handle): the same identity updates in place, a
   second identity on the same core gets its own context (handle@host).

2. `entire auth revoke` honors context-backed logins. The command used
   auth.NewStore() instead of NewContextStore(), so revoke --current missed
   a context-only login, and revoke-by-id's self-revoke cleanup deleted only
   the legacy keyring entry. It now reads through the context store and, when
   it detects it revoked this CLI's own token, clears the active context too
   (threaded as a clearContext func, matching logout).

3. Migration no longer hijacks the active context. RecordLoginContext took
   an `activate` flag: login passes true (use-context semantics), but the git
   helper's read-time MigrateLegacyLoginContext passes false, so a first
   `git clone entire://…` after upgrade records the legacy context without
   silently switching current_context. It still sets current when none
   exists.

Tests: same-core coexistence + re-login-in-place, migration preserves an
existing current context. Full unit/integration/canary green; lint clean
(no --fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 50995fd5dc3f
Second round of review fixes for the multi-context flow:

1. New-cluster resolution prefers the active context. ResolveContextForCluster
   used matches[0] among the issuer's contexts, so a core with several
   accounts (alice@core, bob@core) auto-bound whichever was saved first —
   silently authenticating as the wrong user. It now prefers current_context
   when it's one of the eligible matches, falling back to the first otherwise.

2. Legacy migration keys on (issuer, handle), not issuer alone.
   MigrateLegacyLoginContext skipped when *any* context for the issuer
   existed, so a legacy bob@core was never migrated if alice@core was already
   present. It now skips only when this exact identity is represented.

3. Auth recovery hints name this CLI. The cluster login hint and the
   info/refs 401 message told users to run `entire-core auth login
   --base-url`, but this repo ships `entire` and login isn't a --base-url
   command. They now point at `entire login` / `entire auth use`.

4. Replica cache updates are atomic. discovery.ModifyCache does
   load-mutate-save under a single flock (mirroring contexts.Modify);
   replicas.Persist/Invalidate use it instead of the racy LoadCache + mutate
   + SaveCache, so concurrent clone/fetch/push no longer clobber nodes.json.

Tests: prefer-current resolution, same-core different-handle migration,
ModifyCache atomicity + abort-on-fn-error, updated hint assertions.
Full unit/integration/canary green; lint clean (no --fix).

The clusterdiscovery/discovery/replicas changes are in vendored packages
and should be reconciled upstream in the planned auth-go extraction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 74e46943f957
Third round of review fixes for the multi-context flow:

1. `auth use` no longer over-claims cross-core control-plane switching.
   The active context only retargets which login `git clone entire://…`
   prefers; control-plane commands (auth status/list/revoke,
   org/project/repo/grant) still target the configured auth host
   (ENTIRE_AUTH_BASE_URL / default) because the token manager and coreapi
   client are built against api.AuthBaseURL(), not the context's core. So
   `auth use` now warns when the target context authenticates against a
   different core, and the docs say so plainly. (Full per-context host
   resolution is the deferred multi-core control-plane work.)

2. Logout truly logs out. RemoveCurrentContext let File.Delete advance
   current_context to a surviving context, so `entire logout` (and `auth
   revoke --current`) silently re-authenticated as another account while
   printing "Logged out". It now clears current_context — logged out means
   logged out; other contexts survive and can be re-selected with
   `entire auth use`.

3. Cluster login hint is actionable for non-default cores. `entire login`
   only targets the configured host, so RenderLoginHint now suggests
   `ENTIRE_AUTH_BASE_URL=<url> entire login` (or `entire auth use` for an
   existing login) so users can authenticate against a cluster's
   non-default accepted core.

Tests: cross-core warning (warns vs silent same-core), logout clears
current without switching, hint mentions ENTIRE_AUTH_BASE_URL.
Full unit/integration/canary green; lint clean (no --fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 191830b5fec0
Two follow-up review fixes about the gap between messaging and the
cluster_contexts bindings reality:

1. `auth use` no longer claims clone "uses this context regardless".
   ResolveContextForCluster honors an existing cluster→context binding
   first, so for an already-bound cluster the active context is irrelevant
   to clone/push. The warning and help now say so: a bound cluster keeps
   its binding; the active context applies only to clusters not yet bound.

2. Logout is honest about surviving logins, plus `entire logout --all`.
   A default `entire logout` removes only the active context, but other
   saved contexts (and their cluster bindings) can still authenticate
   clone/push — so "Logged out." overstated it. It now appends a note
   listing the remaining logins and pointing at --all / `auth use`. New
   `entire logout --all` removes every context, its keyring token, and all
   cluster bindings (auth.RemoveAllContexts) for a true full logout.

Tests: RemoveAllContexts (clears contexts + bindings + current, idempotent),
noteRemainingLogins (silent when none, names survivors + --all otherwise).
Full unit/integration/canary green; lint clean (no --fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 18eda08cf5f1
The cross-core warning only fires for different-core switches, so a
same-core `auth use` gave no hint that an already-bound cluster keeps
its existing binding for clone/push. Add a Long help that states the
binding precedence (active context is a fallback; bound clusters keep
their binding) and the control-plane-targeting caveat, so it's
discoverable via `entire auth use --help` for all cases, not only
cross-core. Help-text only; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 7d4609679179
The package/const comments still described the old argv[0]-dispatch +
installer-symlink model. There's now a dedicated cmd/git-remote-entire
binary and BinaryName is used only by it (usage text + git agent
string). Update the docs to match. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 519fe02a690f
Re-vendor githelper/connect.go from entiredb origin/main, which gained
v0/v1 shallow-fetch (`--depth`) support in #1821 after our initial
vendoring. Without it, `git clone --depth=N entire://…` hangs: canonical
git in connect mode parks on a `deepen…` wants-flush waiting for the
server's shallow/unshallow update, and the old relay never fired the
probe POST. The port adds the deepen-triggered probe round and strips the
duplicated shallow prefix the stateless-RPC server emits on each round.

Client-side only and self-contained (upstream go-git pktline/packp +
our gitproto/debuglog); the server half of #1821 is already deployed.
Re-applied our local adaptations (githelper.Agent, serviceUploadPack/
serviceReceivePack consts). The upstream regression test is an e2e that
needs entiredb's server/storage/gittest, so it isn't portable here; the
logic is a verbatim port and githelper unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: a5960da6a540
The helper attaches a core-minted bearer JWT to every data-plane request,
but the replica set it dials was populated from attacker-influenceable
sources with no host validation:

  - the X-Entire-Replicas response header (warm + cold paths),
  - the redirect Location salvage target, and
  - the on-disk node cache (~/.cache/entire/nodes.json).

A malicious entry domain (reachable via a submodule pointing at an
attacker entire:// URL, or a .well-known/entire-cluster.json naming the
victim's real core) could thus advertise replicas on arbitrary hosts and
have the helper POST git-upload-pack to them with the token attached.
Likewise anything running as the user that amends nodes.json could
redirect credentialed requests off-cluster.

checkRedirect already enforces a HostInCluster trust boundary, but only
for HTTP-level redirects, never for the node base URLs the proxy dials
directly. Extend that same boundary to the replica set:

  - filterReplicas drops off-cluster URLs at every ingress (cache load,
    X-Entire-Replicas header, response-URL fallback) so they never enter
    the node set or get persisted;
  - setAuthOrError refuses to stamp a token onto any request bound for an
    out-of-cluster host (defense-in-depth chokepoint);
  - coldInfoRefs refuses an off-cluster Location salvage target.

Scoping is keyed on clusterHost (the host in the user-typed entire:// URL)
and is a no-op when unset, matching checkRedirect. This closes the
token-forwarding leak; it does not change the upstream decision to mint an
aud-bound token for the addressed cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 1eca60eefa33
@Soph Soph force-pushed the soph/git-helper branch from 29d8a91 to 1f289fb Compare June 1, 2026 14:02
@pjbgf pjbgf enabled auto-merge June 1, 2026 14:24
@pjbgf pjbgf merged commit d271618 into main Jun 1, 2026
9 checks passed
@pjbgf pjbgf deleted the soph/git-helper branch June 1, 2026 14:25
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.

3 participants