Skip to content

Say what --project does, and what a mirror ref does not - #2396

Open
Soph wants to merge 1 commit into
mainfrom
soph/repo-path-ref-followups
Open

Say what --project does, and what a mirror ref does not#2396
Soph wants to merge 1 commit into
mainfrom
soph/repo-path-ref-followups

Conversation

@Soph

@Soph Soph commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

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

Five follow-ups to #2387, which took the native /et/<project>/<repo> path into resolveRepoRef and 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.

--project was left in an indefensible middle

The 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 (ListProjectRepos takes 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:

ref --project before
bare name required consulted
/et/<project>/<repo> redundant checked for agreement
ULID meaningless silently ignored

A ULID short-circuits resolveRepoRef before projectRef is ever read, so entire repo get 01ABC… --project a-project-this-repo-is-not-in succeeded 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 needs GetRepo's owningProjectId, an extra round trip on every command here except repo get, which alone already fetches the repo and prints its project. The check rides on bindRepoProjectFlag as a chained PreRunE, 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 false

repo "/gh/entirehq/entire-api" is a GitHub mirror ref; this command addresses Entire-native repos — manage mirrors with entire repo mirror

Several commands sharing this resolver do address mirror repos, by ULID. repo protection list has a purpose-built mirror branch (repo_protection.go:165) that answers one with protectionMirrorNote"GitHub mirror: branch protection is governed by the upstream repository" — which is strictly better than the refusal. And entire 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 add and grant repo remove still told the user a repo is addressed "by name or ULID".
  • The shared --project description said "required when <repo> is a bare name" — advertising a non-bare form that seven of the ten commands never named.
  • CLAUDE.md still framed the native path as clone-only. It now records that ten commands take it, and that the other two clone shapes do not work there (a /gh/ ref is refused, an entire:// URL is not parsed at all) — the asymmetry is the part worth having written down.
  • The #2252 suggestion dropped a --project the user had already supplied: follow did you mean /et/acme/tool? literally with --project widgets still set and the next run fails the agreement check. It now says to drop it.
  • A comment on why the suggestion is not 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 TestCloneRefAlwaysRequiresItsForgePrefix as pinning that no forge-less pair resolves "in either parser or in the command" — but repo clone is no longer the only command parsing a forge-prefixed ref. Its table now runs against resolveRepoRef as 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/web and //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 lint clean. mise run test:ci: all of cmd/entire/cli/... passes. One failure, TestOpenCodeSeedRepoPlantsDeps in e2e/agents, reproduces on clean origin/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 shared resolveRepoRef: it documents what --project is for, surfaces mistakes users could make silently, and fixes a couple of misleading errors.

--project behavior 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 --project with a ULID, commands now print a stderr note that the flag is ignored (still succeeds, for scripted compatibility), wired through bindRepoProjectFlag on all commands that use it. Help text and grant repo long 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-less project/repo pairs, the “did you mean /et/...?” hint can also say to drop --project when 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 + --project warning and the updated mirror/suggestion messages. CLAUDE.md records that the native path works across repo-ref commands, not only repo clone.

Reviewed by Cursor Bugbot for commit 498296f. Configure here.

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
@Soph
Soph requested a review from a team as a code owner September 12, 2026 15:35
Copilot AI lite review requested due to automatic review settings September 12, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 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 --project is 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 --project also 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.

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.

2 participants