fix(cloud-tests): show folder-nested GCP projects in connection picker#2899
Conversation
Customers with an org→folder→project hierarchy (a common SOC2-friendly layout) reported that their production projects never appeared in our GCP connection picker. Two bugs combined to cause it: 1. detectProjectsForOrg filtered with `parent.id:<orgId>`, which GCP's v1/projects API interprets as "immediate org children only". A project whose immediate parent is a folder under the org never matches, even if the user has full IAM access to it. 2. Both detectProjects and detectProjectsForOrg called the v1/projects list endpoint with pageSize=50 and never followed nextPageToken. For accounts with many sandboxes / Gemini default projects / etc., the first 50 results consumed the whole page and the production projects on later pages were silently dropped. Fix: - New `listProjectsPaginated(token, filter)` helper that follows nextPageToken until exhaustion, uses pageSize=200, and stops at a 1000-project safety cap. On a mid-pagination error it returns what it has so far — matches the prior "best-effort" picker posture and prevents a single transient 5xx from blanking the dropdown. - detectProjectsForOrg now issues two parallel paginated calls: one for direct org children (existing behavior) and one for any folder-nested project the caller has access to (`parent.type:folder`). Results merged + deduped. GCP's v1 list API has no "descendants-of-org" query, so the user's IAM scope is the effective filter for the folder-nested arm. - detectProjects refactored to use the same paginator. Direct list is paginated; org-scoped fallback is paginated. Backward compatibility: customers whose projects all live directly under an org see no behavior change (the folder-nested call returns 0 for them). Existing connections aren't touched — detection only runs at connect/re-auth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
1 issue found across 2 files
Confidence score: 3/5
- There is a concrete regression risk in
apps/api/src/cloud-security/providers/gcp-security.service.ts:detectProjectsForOrgappears to broaden scope and return folder-nested projects from all accessible organizations instead of only the requestedorganizationId. - Because this is a medium-severity, high-confidence behavior change (6/10, 8/10), it could lead to user-visible cross-organization results and incorrect project targeting, so some validation is needed before merging.
- Pay close attention to
apps/api/src/cloud-security/providers/gcp-security.service.ts- ensure organization filtering is strictly enforced to the selectedorganizationIdwhen traversing folder-nested projects.
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
…picker Switches `detectProjectsForOrg` from `Promise.all` → `Promise.allSettled` so a transient failure on the new folder-nested arm (e.g., GCP rejects the `parent.type:folder`-alone filter, DNS blip, transient 5xx during pagination) cannot blank the entire picker. The direct arm matches the prior production code path, so as long as it succeeds we are guaranteed to be no worse than today's prod even if the folder arm fails outright. Two new tests lock in the isolation: - direct arm succeeds, folder arm throws → returns direct results - folder arm succeeds, direct arm throws → returns folder results Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
…rget org Addresses cubic P2 on PR #2899: the previous folder arm filter (`parent.type:folder` alone) would have returned projects from ANY org the caller had access to, violating the `detectProjectsForOrg` contract for multi-org users. The folder arm now recursively enumerates folders under the target org via `v2/folders?parent=organizations/<orgId>` (and recursively under each child folder), then queries each discovered folder with `parent.type:folder AND parent.id:<folderId>` — the paired filter shape GCP explicitly documents for by-parent project queries and which triggers their alternate consistent index. Behaviors preserved: - Promise.allSettled isolation so a failure on either arm cannot blank the picker. - Per-folder query failures are isolated (one bad folder doesn't kill the rest). - Safety cap of 500 folders to bound API usage. New tests: - Greg's exact scenario: only this org's folder gets queried. - Recursive sub-folder traversal (org → folder → sub-folder → projects). - Dedupe across arms. - Folder arm failure → direct arm still works. - Direct arm failure → folder arm still works. - Per-folder failure isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
Addresses cubic P2 on PR #2899: the previous `Promise.all` over every folder ID fires N simultaneous requests to cloudresourcemanager. For tenants with many folders, that can trip GCP's per-user read-quota, and because `listProjectsPaginated` returns the projects-collected-so- far on a non-OK response (including 429 Too Many Requests), a throttled folder query LOOKS like an empty folder — silently truncating the picker with no visible error. The fix bounds concurrent in-flight folder queries to 5 via a small inlined `mapWithConcurrency` helper. GCP's read quota (~600 req/min/user on cloudresourcemanager) is well above this, so the cap stays comfortably below throttling thresholds even for very deep folder hierarchies. Test added that builds a 20-folder tenant, holds the per-folder project queries open, and verifies: - peak in-flight count never exceeds 5 - all 20 folder queries eventually run (cap doesn't truncate work) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
|
🎉 This PR is included in version 3.61.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Fixes a customer-reported (Greg @ Propper) bug where GCP projects nested inside a folder (org → folder → project) never appeared in the connection picker, even though the user had full IAM access.
Root cause — two bugs combined:
detectProjectsForOrgfiltersparent.id:<orgId>which GCP's v1 list endpoint interprets as immediate children only — a project whose immediate parent is a folder under the org never matches.v1/projectswithpageSize=50and never followednextPageToken. Accounts with many sandboxes / Gemini default projects / etc. hit the page cap before reaching production projects on later pages, silently dropping them.Greg's exact reproduction: org
43356919874(propper.ai), folder9724350536(propper), projectspropperai-prod+propperai-demounder that folder — invisible in picker, despitegcloud projects list --filter="parent.id=9724350536"returning both.What changed
New
listProjectsPaginated(token, filter)helper:nextPageTokenuntil exhaustion (pageSize=200, well under GCP's 500 max).detectProjectsForOrgnow issues two parallel paginated calls and merges + dedupes:parent.id:<orgId>) — preserves existing behavior.parent.type:folder) — the new arm that surfaces Greg's projects. GCP's v1 list API has no "descendants-of-org" query, so the user's IAM scope is the effective filter here.detectProjects(no-org-known path) refactored to use the same paginator. Direct list paginated; org-scoped fallback paginated. Otherwise unchanged.Backward compatibility
resourcemanager.projects.listthe picker has always used.Tests
9 new unit tests covering:
nextPageTokenend-to-end.pageTokenis correctly propagated to subsequent requests.detectProjectsForOrgreturns the union of direct + folder-nested.detectProjectsForOrgdedupes overlapping results.detectProjectsForOrgrun in parallel (Promise.all).[].cd apps/api && npx jest src/cloud-security→ 242/242 pass (one pre-existing suite failure onremediation.controller.spec.tsis the known Postgres-TLS env issue, identical onmain).Manual test plan
propperai-prodandpropperai-demoappear in pickerIndependent of in-flight PR
This is on its own branch off
main(tofik/fix-gcp-project-picker-pagination) — unrelated to PR #2885 (auto-remediate fixes).🤖 Generated with Claude Code
Summary by cubic
Show folder‑nested GCP projects in the connection picker, paginate project listing for complete results, scope folder queries to the selected org, and cap per-folder queries to avoid throttling and silent drops.
listProjectsPaginated(token, filter)that followsnextPageToken, usespageSize=200, caps at 1000, and returns partial results on errors.detectProjectsForOrgto run two paginated queries in parallel and merge/dedupe: direct org children, plus folder‑nested projects scoped to that org by recursively enumerating folders viav2/folders?parent=organizations/<orgId>and querying each withparent.type:folder AND parent.id:<folderId>. UsesPromise.allSettledso either arm failing doesn’t blank the picker.429) that could look like empty folders.detectProjectsto use the same paginator for direct and org‑scoped fallback paths. Existing permissions and behavior for orgs without folder nesting remain unchanged.Written for commit d333f59. Summary will update on new commits. Review in cubic