Say what --project does, and what a mirror ref does not - #2396
Open
Soph wants to merge 1 commit into
Open
Conversation
Follow-ups to #2387, which took the native /et/<project>/<repo> path into resolveRepoRef and so into every command that resolves a repo ref. --project was left in an indefensible middle. It serves the bare-name spelling alone: a repo name is unique only within a project, and the control plane has no by-name route that is not project-scoped, so without the flag a bare name is not an address. The other two spellings carry their own project, and the two disagreed about saying so. A path ref is checked for agreement, which is free because the resolver looks that project up anyway; a ULID short-circuits before projectRef is ever read, so a flatly wrong project was accepted in silence. Warn on the ULID rather than validating it — validation needs GetRepo's owningProjectId, an extra round trip on every command here but `repo get` — and warn rather than refuse, because the combination has been accepted since the flag shipped in June and is plausibly scripted. The hook rides on bindRepoProjectFlag, so it cannot be wired for some of the ten commands and missed for others. The /gh/ refusal claimed "this command addresses Entire-native repos", which is false: several commands sharing this resolver address mirror repos by ULID, and `repo protection list` answers one with protectionMirrorNote — branch protection is governed upstream — which is a better answer than the refusal gave. `entire repo mirror`, where it sent them, has no protection or visibility counterpart. What genuinely cannot work is the by-name lookup: it resolves a project and then a repo inside it, and a mirror is in no project. So the message now describes the ref shape and names the ULID as the way through. The rest is text that went stale in the same change: `grant repo add` and `grant repo remove` still told the user a repo is addressed "by name or ULID", and CLAUDE.md still framed the native path as clone-only when it is now accepted by ten commands — while the other two clone shapes are not. TestCloneRefAlwaysRequiresItsForgePrefix follows the requirement rather than the command, so its table now runs against resolveRepoRef too, with and without --project. The invariant already held; nothing pinned it there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2B44CAQC59EYS12RNXXM8X3
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The /gh/... error still inaccurately implies only /et/... references resolve by name; it should describe the actual mirror-by-name restriction.
Pull request overview
This PR clarifies repository reference syntax, improves resolver diagnostics, and updates related documentation and tests.
Changes:
- Warns when
--projectis used with a ULID. - Refines mirror-ref and forge-less-reference errors.
- Updates help text, tests, and
CLAUDE.md.
File summaries
| File | Summary |
|---|---|
cmd/entire/cli/resolveref.go |
Refines repository reference errors and suggestions. |
cmd/entire/cli/resolveref_test.go |
Tests updated resolver messaging. |
cmd/entire/cli/repo.go |
Adds the redundant --project warning. |
cmd/entire/cli/repo_test.go |
Tests warning behavior and command wiring. |
cmd/entire/cli/repo_clone_test.go |
Extends forge-prefix guard coverage. |
cmd/entire/cli/grant.go |
Documents accepted repository reference forms. |
CLAUDE.md |
Documents shared resolver behavior and limitations. |
Review details
Suppressed comments (1)
cmd/entire/cli/resolveref.go:262
- This error text is still misleading: bare repository names with
--projectalso resolve by name in this same resolver, so saying that only the/et/...path resolves by name can send users toward a path form they do not need. Describe the actual restriction on the/gh/shape instead (for example, that mirror refs cannot be resolved by name here) while keeping the ULID guidance.
return "", fmt.Errorf("repo %q is a GitHub mirror ref; only the native /%s/<project>/<repo> path resolves by name here — pass the repo's ULID instead, or see `entire repo mirror` for the mirror-specific commands", ref, nativeCloneForge)
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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/1318
Five follow-ups to #2387, which took the native
/et/<project>/<repo>path intoresolveRepoRefand so into every command that resolves a repo ref — ten of them, not the two whose help text was updated. No behaviour that #2387 added changes here; what changes is the one spelling it left silent, one message that was wrong, and the text that went stale around it.--projectwas left in an indefensible middleThe flag serves the bare-name spelling alone. A repo name is unique only within a project, and the control plane has no by-name route that is not project-scoped (
ListProjectRepostakes the project as a path segment), so without the flag a bare name is not an address at all. The other two spellings carry their own project — and the two disagreed about saying so:--project/et/<project>/<repo>A ULID short-circuits
resolveRepoRefbeforeprojectRefis ever read, soentire repo get 01ABC… --project a-project-this-repo-is-not-insucceeded cleanly and confirmed a wrong belief. It now says so on stderr.It warns rather than refusing because the combination has been accepted since the flag shipped (
ece9fb3dc, June 2026) and is plausibly scripted — breaking that to report a flag that was already being ignored is a poor trade. It does not validate because that needsGetRepo'sowningProjectId, an extra round trip on every command here exceptrepo get, which alone already fetches the repo and prints its project. The check rides onbindRepoProjectFlagas a chainedPreRunE, so it cannot be wired for some of the ten commands and missed for others; a test asserts all ten carry it.The
/gh/refusal said something falseSeveral commands sharing this resolver do address mirror repos, by ULID.
repo protection listhas a purpose-built mirror branch (repo_protection.go:165) that answers one withprotectionMirrorNote— "GitHub mirror: branch protection is governed by the upstream repository" — which is strictly better than the refusal. Andentire repo mirror, where it sent them, has no protection or visibility counterpart, so it is a dead end from two of the four families.What genuinely cannot work is the by-name lookup: it resolves a project and then a repo inside it, and a mirror is in no project. The message now describes the ref shape and names the ULID as the way through.
Stale text from the same change
grant repo addandgrant repo removestill told the user a repo is addressed "by name or ULID".--projectdescription said "required when<repo>is a bare name" — advertising a non-bare form that seven of the ten commands never named./gh/ref is refused, anentire://URL is not parsed at all) — the asymmetry is the part worth having written down.#2252suggestion dropped a--projectthe user had already supplied: followdid you mean /et/acme/tool?literally with--project widgetsstill set and the next run fails the agreement check. It now says to drop it.bareRefSuggestions: that helper also offers the/gh/reading, which this resolver refuses outright, and a suggestion that fails on the next run is worse than none.The guard test follows the requirement, not the command
CLAUDE.md describes
TestCloneRefAlwaysRequiresItsForgePrefixas pinning that no forge-less pair resolves "in either parser or in the command" — butrepo cloneis no longer the only command parsing a forge-prefixed ref. Its table now runs againstresolveRepoRefas well, with and without--project, so the flag cannot become a way to have a bare pair read as a name inside that project.I measured this before writing it: every entry in that table, plus
github.com/acme/tool,https://github.com/…,git/acme/tool,ET/widgets/weband//et/widgets/web, is already refused with zero HTTP calls. The namesquatting invariant held; nothing pinned it at the second entry point.Verification
mise run fmt && mise run lintclean.mise run test:ci: all ofcmd/entire/cli/...passes. One failure,TestOpenCodeSeedRepoPlantsDepsine2e/agents, reproduces on cleanorigin/main(opencode dep-seeding cache) and is untouched by this change.🤖 Generated with Claude Code
Note
Low Risk
User-facing help, stderr warnings, and local validation messages only; no changes to resolution logic beyond clearer errors and the new ULID + --project notice.
Overview
This PR tightens repo ref UX after native
/et/...paths landed in sharedresolveRepoRef: it documents what--projectis for, surfaces mistakes users could make silently, and fixes a couple of misleading errors.--projectbehavior is now explicit. The flag only applies to bare repo names;/et/<project>/<repo>paths are checked for agreement and repo ULIDs ignore it. When someone passes--projectwith a ULID, commands now print a stderr note that the flag is ignored (still succeeds, for scripted compatibility), wired throughbindRepoProjectFlagon all commands that use it. Help text andgrant repolong descriptions now list the three accepted spellings (path, bare name +--project, ULID).Resolver errors are corrected. Refusing
/gh/...mirror refs no longer claims the command only works on native repos; the message points at by-name limits and using the repo ULID (mirror repos aren’t in a project). For forge-lessproject/repopairs, the “did you mean/et/...?” hint can also say to drop--projectwhen it would clash on the next run.Tests and docs follow the shared resolver. Clone’s forge-prefix guard table also runs against
resolveRepoRef(with and without--project). New tests cover the ULID +--projectwarning and the updated mirror/suggestion messages.CLAUDE.mdrecords that the native path works across repo-ref commands, not onlyrepo clone.Reviewed by Cursor Bugbot for commit 498296f. Configure here.