repo: a repo shared by direct grant now clones by its /et/ path - #2543
Conversation
A repo shared with one person by a direct repo grant could not be cloned by that person: `entire repo clone /et/<project>/<repo>` looked the project up first (GET /projects?name=), which needs project#inspect, and a repo-only grant does not confer it. The server answered 403 and the CLI printed "permission denied" before it ever asked about the repo. Resolve the name pair through POST /repos/resolve instead. That route is gated on repo#pull, the permission cloning needs, and returns the repo ULID in one call. GetRepo (also pull-gated) still supplies clusterHost and path. Every /et/ ref command goes through the same helper, so view, delete, visibility, protection, grant, and the cell-routing commands are fixed with clone. Only a --project ULID with a bare name still takes the project-scoped listing, since there is no ULID-to-name route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Full unit run on the branch head ( Server-side proof for the case in the description, from Datadog Every |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It changes a central repo-ref resolution path used by multiple commands and permission boundaries, so a final human verification of all affected flows is warranted.
Review effort: Lite
Findings: None
What changed in this PR
This PR fixes cloning (and other repo-ref commands) for repositories shared via direct repo grants by changing /et/<project>/<repo> resolution from project-scoped lookups (requiring project#inspect) to the pull-gated POST /repos/resolve, then using GetRepo only where additional repo metadata is needed.
Changes:
- Replace native
/et/<project>/<repo>name resolution withPOST /repos/resolvevia a shared helper (resolveNativeRepoByPath) to avoid project permission gating. - Update repo ref agreement-check behavior so
--project <ULID>alongside a native path validates viaGetRepo(owning project id), while--project <name>is checked before any request. - Adjust and extend fixtures/tests to pin the regression (native refs must not hit
/projectslookups; “unavailable” resolves to a single “not found or not shared” message).
| File | Description |
|---|---|
| docs/development/cli-conventions.md | Updates documentation to reflect the new pull-gated native ref resolution flow and agreement checks. |
| cmd/entire/cli/resolveref.go | Implements native repo path resolution via POST /repos/resolve and updates path/project agreement logic. |
| cmd/entire/cli/resolveref_test.go | Reworks resolver fixtures to serve /repos/resolve, forbids project lookups, and adds coverage for “unavailable” behavior. |
| cmd/entire/cli/repo_clone.go | Routes native clone resolution through the new path lookup helper to avoid ULID-shaped name pitfalls. |
| cmd/entire/cli/repo_clone_test.go | Updates native clone fixture to use /repos/resolve and removes now-invalid project-scoped endpoints. |
| cmd/entire/cli/grant_wiring_test.go | Updates grant wiring fixture to resolve /et/ refs via /repos/resolve. |
| cmd/entire/cli/cell_target.go | Aligns native cell placement resolution commentary and uses the shared native resolver client surface. |
| cmd/entire/cli/cell_target_test.go | Refactors fake core to support /repos/resolve and explicitly fail any project-scoped lookup attempts for native refs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A bare repo name under a non-ULID --project resolves through repos/resolve, not the project-scoped listing. Nothing drove that branch: the name subtests pass a project ULID and the path subtests enter through resolveRepoPathRef. Add the hit and the miss so routing it back onto /projects fails CI. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M333AD4M204170HYY9JAYKBE
https://entire.io/gh/entireio/cli/trails/1388
Why
Share one private repo with one person and they cannot clone it:
The grant is correct. The CLI resolves a
/et/<project>/<repo>ref by looking the project up first (GET /projects?name=proj), and that route needsproject#inspect. A direct repo grant confersrepo#pullonly, so core answers 403 before the repo is ever asked about. Logs today showuser2hitting exactly that 403 four times.The only way through was to give the grantee read on the whole project, or hand them the raw
entire://URL.What
Resolve the name pair through
POST /repos/resolveinstead. That route is gated onrepo#pull, the permission cloning needs, and returns the repo ULID in one call.GetRepo(also pull-gated) still suppliesclusterHostandpath. No server change.Every
/et/ref shares the helper, sorepo view,delete,visibility,protection,grant, and the cell-routing commands (trail,search, …) are fixed withclone. Two round trips instead of three. A repo that does not resolve reports one thing, because the server answers unknown and unshared alike:Only
--project <ULID>with a bare repo name still uses the project-scoped listing, since there is no ULID→name route.Verification
go test ./cmd/entire/cli/green. The fixtures now 403 any/projectscall a native ref makes, which is the regression pin.mise run lint:go,lint:gofmt,lint:gomod,dupclean.lint:shellcheckfails on untouched scripts with local shellcheck 0.11; unrelated.POST /api/v1/repos/resolve {"repositories":[{"provider":"entire","fullName":"proj/repo"}]}against prod returnsstatus: ready, the repo ULID and its placement.🤖 Generated with Claude Code
Note
Medium Risk
Touches shared repo-ref resolution used by clone, grants, and cell routing; behavior change for permission boundaries but aligned with server pull-gated API and heavily test-pinned.
Overview
Fixes direct repo grantees who could not use
/et/<project>/<repo>because the CLI resolved names via project-scoped APIs that requireproject#inspect, while grants only conferrepo#pull.Native
<project>/<repo>pairs now resolve in onePOST /repos/resolvecall (resolveNativeRepoByPath), shared by clone, grant, cell routing, and otherresolveRepoRef/resolveRepoPathcallers. That drops a round trip versus the old project→repo chain and avoids hitting the forge-blind repos index for/et/paths. Misses surface asrepo /et/... not found or not shared with youwhen the server returns unavailable (unknown vs unshared are intentionally indistinguishable).--project <ULID>+ bare repo name still usesListProjectReposbecause the path API expects names, not project ids.Tests and grant wiring fixtures now stub
/repos/resolveand 403 any accidental/projectslookup as the regression pin.Reviewed by Cursor Bugbot for commit f694a33. Configure here.