fix(grant): resolve repo by name via the singular `repo` field (COR-699) · Entire

fix(grant): resolve repo by name via the singular `repo` field (COR-699)

71e6e4c·

toothbrush·2w ago·2 files·+14 added/-7 removed

The control plane returns a name-filtered repo list under the singular `repo` field (like org/project), not the plural `repos` array — `repos` is only populated for an unfiltered page. resolveRepoRef read `repos`, so `grant repo {list,add,remove} --project

` always 404'd with "no repo named" even when the repo existed. The fixture mocked the plural array too, hiding it; it now mirrors the real server's singular field.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

0a7fba82dafbView transcript

[?
Improve Grant CLI Verb Syntax and HandlingClaude Code·Opus 4.8[1m]·1 step](/content/gh/entireio/cli/session/fbd23c93-1510-47c9-9201-6c325b4f3251#timeline-0a7fba82dafb/index.html)

Changes

2

171 unmodified lines

172
173
174
175
176
177
175
176
177
178
179
180
181
12 unmodified lines

194
195
196
196
197
198
199
200
199
201
202
203
204

171 unmodified lines

// resolveRepoRef turns a repo reference into its ULID. A ULID passes through.
// A name requires a project scope (projectRef, itself a name or ULID) because
// repo names are unique only within a project: the repo is resolved via the
// server's case-insensitive by-name lookup, scoped to that project. Unlike the
// org/project endpoints, the repo list response has no singular field, so a
// name-filtered query returns the single match (or none) under `repos`.
// server's case-insensitive by-name lookup, scoped to that project. Like the
// org/project endpoints, a name-filtered list returns the single match under the
// response's singular \`repo\` field (the plural \`repos\` is only populated for an
// unfiltered page) — reading \`repos\` here was the COR-699 bug.
func resolveRepoRef(ctx context.Context, c *coreapi.Client, ref, projectRef string) (string, error) {
    if looksLikeULID(ref) {
        return ref, nil
12 unmodified lines

}
    return "", err
    }
    if len(out.Repos) == 0 {
    repo, ok := out.Repo.Get()
    if !ok {
        return "", noRepoNamedErr(ref)
    }
    return out.Repos[0].ID, nil
    return repo.ID, nil
}

func noOrgNamedErr(name string) error {

Mcmd/entire/cli/resolveref.go+7/-5

203 unmodified lines

204
205
206
207
208
207
208
209
210
211
212
213
214
215
216

203 unmodified lines

var gotName string
    c, calls := resolveTestClient(t, func(w http.ResponseWriter, r *http.Request) {
        gotName = r.URL.Query().Get("name")
        if err := writeJSON(w, &coreapi.ListProjectReposOutputBody{Repos: []coreapi.Repo{{ID: ulidRepoWeb, Name: "web"}}}); err != nil {
            t.Errorf("encode repos: %v", err)
        // A name-filtered list returns the single match under the singular
        // \`repo\` field (like org/project) — NOT the plural \`repos\` array,
        // which is only populated for an unfiltered page. Reading \`repos\`
        // here was the COR-699 bug, so the fixture must mirror the real
        // server's singular field to keep that regression covered.
        if err := writeJSON(w, &coreapi.ListProjectReposOutputBody{Repo: coreapi.NewOptRepo(coreapi.Repo{ID: ulidRepoWeb, Name: "web"})}); err != nil {
            t.Errorf("encode repo: %v", err)
        }
    })
    // Project passed as a ULID so resolveProjectRef short-circuits (no call);