fix(cli): resolve list/wait projects by repo URL, not directory name (COR-1577) - #137
Conversation
COR-1577. `corgea list` and standalone `corgea wait` derived the project from the working-directory basename and sent it as an exact `?project=` match. Where the stored name differs — the Bank of Hope case, dir `dotnet-azure-web-tsb` vs project `bohappdev/dotnet-azure-web-tsb` — `list --issues` and `wait` exited 1 and plain `list` silently printed an empty table. Both now resolve the canonical project from the git remote: `GET /api/v1/projects?repo_url=<org/repo>`, discovered upward from the CWD so a subdirectory works too, then query the listing endpoints by the name the backend actually stores. - The backend filters `repo_url__icontains`, so every candidate is re-checked against the whole post-host path. That also guards a pre-COR-1426 backend, which ignores the unknown param and returns ALL projects: none match, and we fall back rather than list a stranger's scans. - Unconfirmed, the query stays exactly what the pre-COR-1577 CLI sent, so an old or not-yet-onboarded backend keeps working. - `--project-name` queries an exact name and skips resolution entirely; `--repo` resolves from a given slug or URL instead of the remote. - An unresolved project with no scans is now an error naming what was tried, instead of an empty table; a confirmed project with no scans still exits 0 so CI polling is unaffected. - The client-side `scan.project == cwd_basename` filter is gone — the server already filtered, and that pass discarded every resolved scan.
0184a26 to
1a543f4
Compare
There was a problem hiding this comment.
Automated review risk: 4/5.
Five earlier comments are addressed, but two high-priority wait-path defects remain: canonical project links are malformed and explicit scan IDs unnecessarily depend on repository resolution.
Critical or high-priority changes must be addressed.
Automatic approval was not submitted: automated review found critical or high-priority findings.
There was a problem hiding this comment.
Automated review risk: 4/5.
Core resolution logic is well tested, but two high-priority wait paths remain broken. Earlier resolved comments are addressed by the supplied diff.
Critical or high-priority changes must be addressed.
Automatic approval was not submitted: automated review found critical or high-priority findings.
db2e750 to
eaf745d
Compare
…resolution when a scan id is given, honest empty-list copy
There was a problem hiding this comment.
No actionable findings.
Verified the merge-base diff at b41f553, including every wait::run call site, the list/wait empty and error paths, repository URL normalization and exact-path matching, the /projects response contract, and the new request-target assertions. The latest commit addresses the prior wait-path findings: explicit scan IDs no longer depend on project resolution, and canonical project names are percent-encoded in result links. All prior automation threads are resolved.
Validation passed locally: ./harness test (556 tests), ./harness lint (Clippy and format), and git diff --check. CI is also green across Rust tests and supported build platforms.
Sent by Cursor Automation: pr-flow
| let had_scp_colon = url[..first_slash].contains(':'); | ||
| // URL forms split host from path on '/', scp-like `git@host:org/repo` on ':'. | ||
| let mut segments: Vec<&str> = url.split(['/', ':']).filter(|s| !s.is_empty()).collect(); | ||
| // segments[0] is the host; an all-digit segment right after it is a port. |
There was a problem hiding this comment.
nit: this might be an edge case but it's not always entirely true that segments[1] is a port, for example https://gitlab.com/101/team-alpha/my-repo or git.company.internal/9082/project-x
leenk7991
left a comment
There was a problem hiding this comment.
lgtm, can we make the comments and docs more concise
* refactor(cli): pass list/wait arguments as structs
`wait::run` took five positional params, four of them Option<String>;
`repo` and `project_id` were adjacent and same-typed, so transposing them
at the scan.rs call sites compiled silently and changed resolution.
`list::run` took nine and carried #[allow(clippy::too_many_arguments)].
- `ProjectSelector { name, repo }` replaces the two Option<&str> resolver
params, so the pair travels as one value.
- `WaitArgs`/`ListArgs` name every argument at the call sites; the
too_many_arguments allow is gone rather than institutionalized.
- list::run now owns its arguments, so two `is_some()` + `unwrap()` pairs
clippy started flagging become `if let Some(id)`.
* fix(cli): page through /projects, fail closed, disambiguate by host
Three ways the single-page resolver could quietly answer with the wrong
project, all of which end in the legacy-name fallback listing someone
else's scans:
- `/projects` is `@paginated(default_page_size=20, max_page_size=50)` over
a `repo_url__icontains` filter ordered `-created_at` (doghouse
api/views/core.py:1640-1683, api/decorators.py:158). Enough `acme/api-*`
siblings pushed the exact `acme/api` off page 1. Now requests
page_size=50 and walks until an exact match or the last page; stopping
early at PROJECTS_MAX_PAGES is an error, not a miss, since the search was
truncated.
- A 200 whose body does not parse, or which omits `projects` entirely
(`{"status":"error"}`), read as "no matches". `@paginated` emits the key
on every 200 including the empty case, so both now fail the parse.
- Two projects can share a path across forges. The host settles it — but as
a tie-breaker, never a gate: a lone path match is accepted whatever its
host, so an SSH-config alias origin (`corp-github:org/repo`, the shape
this repository's own remote uses) still resolves. Several matches with
none on our host errors and names the competing URLs.
* fix(cli): encode the issues query, add --project-id, scope SCA to the selector
- `get_scan_issues` interpolated the project name straight into the query
string, so `--project-name 'foo&bar'` sent `?project=foo&bar&page=1` and
the server read the project as `foo` — returning another project's issues
under the name the caller asked for. Built with `query` now, like
`query_scan_list` and `get_sca_issues`.
- `--project-id` exposes the id `wait` already accepted internally from an
upload response. Paired with a scan id it skips resolution entirely,
which is what a CI job passing the id between steps wants. clap requires
the scan id: without one the scan is still picked by the resolved name,
so a lone --project-id would only relabel the link.
- `--project-name`/`--repo` were accepted with `--sca-issues` and then
ignored: the SCA branch returns before any resolution and
`get_sca_issues` sent only pagination, so a script asking for one project
silently got company-wide findings. `list_sca_issues` does take `project`
(doghouse api/views/core.py:1179-1195), so the resolved name is threaded
in. Only an explicit selector scopes it — unflagged `--sca-issues` keeps
returning the company-wide latest scan, since narrowing that default is a
separate decision.
* test(cli): share the resolution e2e stub harness
`spawn_stub` was a near-duplicate across list_resolution.rs and
wait_resolution.rs, and the two files hand-rolled the same route matching.
`common::Routes` is one table for every endpoint the two suites stub; an
unset field 404s, so "this endpoint is never dialed" tests just leave it
out. Tests needing an arm outside the table (blocking rules, the scan
upload) match it first and delegate to `Routes::answer`.
Scaffolding only: no assertion changed.
* fix(cli): link the scan's canonical project on the wait scan-id path
* fix(cli): collect host matches instead of returning the first one
Two projects claiming the same host+path now hit the ambiguity error
instead of resolving to whichever the backend listed first. Also trims
review-flagged comments.
---------
Co-authored-by: Test <test@example.com>


Fixes COR-1577. Replaces #122, which grew well past the ticket; this is the fix on its own, and the hardening follows in a stacked PR.
The bug
corgea listand standalonecorgea waitderived the project from the working-directory basename and sent it as an exact?project=match. Where the stored name differs — the Bank of Hope case, directorydotnet-azure-web-tsbvs projectbohappdev/dotnet-azure-web-tsb—list --issuesandwaitexited 1, and plainlistsilently printed an empty table.The fix
Resolve the canonical project from the git remote —
GET /api/v1/projects?repo_url=<org/repo>, discovered upward from the CWD so a subdirectory works — then query the listing endpoints by the name the backend actually stores.Three things make it safe on older and partially-onboarded backends:
repo_url__icontains, so a query foracme/apialso returnsacme/api-v2and the nestedmirrors/acme/api. Every candidate is re-checked against the whole post-host path. The same check guards a pre-COR-1426 backend, which ignores the unknown param and returns all projects: none match, so we fall back instead of listing a stranger's scans.determine_project_name), so an old or not-yet-onboarded backend keeps working.Escape hatches:
--project-namequeries an exact name and skips resolution entirely;--reporesolves from a given slug or URL instead of the remote. Both are onlistandwait.Also removes the client-side
scan.project == cwd_basenamefilter — the server already filtered, and that pass discarded every resolved scan.Deliberately not here
Kept out to hold this to the ticket; all of it lands in the stacked follow-up:
/projects(page 1 only today — see COR-1729, which would let the CLI drop both the walk and the re-check)org/repoon two forges/projectsenvelopes/issues(pre-existing)--project-id, SCA selector scoping, argument structsVerification
cargo build,cargo clippy --all-targets(clean),cargo fmt --check, and the full suite underCI=1 GITHUB_ACTIONS=true— 537 tests.16 new e2e tests cover: the canonical name driving
/scansand/issues(asserted on the request the CLI sent, from a checkout namedbuild-123, so a fallback cannot pass); resolution from a subdirectory;--repo; the miss naming the repo with no table; confirmed-empty exiting 0; the legacy fallback name;--project-nameskipping resolution; the JSON envelope preceding the miss;--scan-idskipping resolution; and the post-scancorgea scanpath resolving nothing.Size
429 lines of production code (~340 excluding comments and blank lines), 825 of tests.