fix(github-projects): route gh to the repo's host in multi-host setups (#1715)#6583
fix(github-projects): route gh to the repo's host in multi-host setups (#1715)#6583nwparker wants to merge 3 commits into
Conversation
#1715) GitHub Projects discovery, view fetches, slug mutations, and the gh auth diagnostic all issued hostless `gh api graphql` / `gh auth status` calls, so gh fell back to its globally-active host (typically github.com) instead of the repo's host. In a github.com + GHES setup this made Projects report a false "missing project scope" error and rejected pasted GHES project URLs. Thread an optional GitHubRepoTarget {repoPath, connectionId} from the renderer through IPC/RPC, resolve the repo's git-remote host, and pass `gh api --hostname <host>` (with cwd for repo-context placeholders). The host resolver ignores known non-GitHub remotes (GitLab/Bitbucket/Azure) so ProjectV2 calls are never mis-routed. ProjectPicker now accepts any host so GHES project URLs parse, and the pasted URL's host overrides the active-repo hint. Renderer caches are keyed by the routing target so the same owner/project on different hosts can't reuse each other's data. Supersedes #2784 (community PR re-implemented against current main). Fixes #1715 Co-authored-by: Borja <b.llanderas@gmail.com>
|
Warning Review limit reached
Next review available in: 6 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (32)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Test & gate evidenceRouting unit tests (the regression guards)
Key assertions covered:
Full github + rpc-methods regression sweep
Local gate
Before / after reasoningBefore: After: the renderer passes the active repo's |
Mergeability follow-upPushed The conflict resolution keeps both sides:
Cache note: route-targeted renderer caches intentionally fetch fresh data when their routing key changes. That is normal cache invalidation for host isolation, not an unresolved PR tradeoff; sharing those entries would reintroduce the bug class because same owner/project numbers can exist on different GitHub hosts. Explicit Validation after merge:
GitHub mergeability after push: REST reports |
…583-zero-tradeoff # Conflicts: # src/renderer/src/components/GitHubItemDialog.tsx
🧑🤝🧑 Impact & ELI5
1) What's broken today (ELI5). People who sign into more than one GitHub at once — the public github.com and a company-private "GitHub Enterprise" server — cannot use GitHub Projects in Orca for a repo that lives on the company server. Orca pops up a scary, wrong error: "Your gh token is missing the project scope…" even though the company login already has that permission. On top of that, the suggested fix names the wrong account, and pasting a company Project link is flat-out rejected. GitHub Issues already worked fine in the same setup, so it looks especially broken and confusing. This is a hard blocker (Projects is unusable), but only for the niche group running a true two-GitHub setup; everyone on plain github.com is unaffected.
2) What this PR does (ELI5). Think of
gh(the GitHub command-line tool Orca calls under the hood) as a courier. For Issues, Orca handed the courier the repo folder so it could read the return address and deliver to the right post office. For Projects, Orca forgot to include the address, so the courier defaulted to the main github.com post office — the wrong one. This PR figures out the repo's real "post office" from its git remote and writes it explicitly on every Projects-related request (gh ... --hostname <that-server>), including the auth diagnostic and the paste-a-Project-link box. So Orca now talks to the GitHub server that actually owns your repo instead of always defaulting to public github.com. It is careful to skip non-GitHub remotes (GitLab, Bitbucket, Azure) so those are never mis-addressed.3) Regression answer. No feature is disabled or degraded. Caches are deliberately partitioned by the repo routing target, so Project data fetched for one GitHub host is never reused for a same-named project on another host. On upgrade, the new cache keys naturally fetch fresh data the first time they are used; that is normal cache invalidation for correctness, not a user-visible regression. Plain github.com users now route to github.com explicitly, which preserves behavior and also protects them when
gh's active/default host is set to a different GitHub host. Non-GitHub hosts are rejected or dropped beforeghis invoked.Summary
Fixes #1715. In multi-host gh setups (github.com + a GitHub Enterprise instance), GitHub Projects was unusable: discovery, view fetches, slug-addressed mutations, and the
gh auth statusdiagnostic all issued hostlessgh api graphql/gh auth statuscalls. gh therefore fell back to its globally-active host (usually github.com) instead of the repo's own host. The result:project.https://ghe.example.com/orgs/acme/projects/2) was rejected because the picker hardcodedgithub.com.(Repo-scoped Issues already worked because they run with
cwd=repoPath, which lets gh infer the host — Projects' hostless GraphQL calls did not.)This threads an optional
GitHubRepoTarget{ repoPath, connectionId }from the renderer through IPC/RPC into the main process, resolves the repo's git-remote host, and passesgh api --hostname <host>(keepingcwdfor repo-context placeholders and forgh issue close/reopen, which has no--hostnameflag). Concretely:normalizeGitHubApiHost/preferredGitHubApiHostadded to the existinggithub-remote-identity-parsing.ts; a focusedgithub-api-host-resolution.tsresolves and caches the repo's gh API host (prefersupstreamthenorigin).github/ghehosts.targetToGhApiRoute+GhApiRouteinproject-view/internals.ts;runGraphql/runRestnow take a route; all discovery / view / count / mutation / auth-status gh calls are routed.ProjectPickeraccepts any host so GHES URLs parse; a pasted URL's host overrides the active-repo hint (resolveProjectRef). Host validation rejects a leading dash and thegithub.com@evil.examplespoof.projectViewCacheKey/request key) are keyed by the routing target so the same owner/project on different hosts can't reuse each other's data.This supersedes #2784 (community PR by @BorjaLL): re-implemented against current
main(the original no longer applied across 8 files), reusing the existing remote-identity parser instead of adding a duplicate module, and dropping that PR's formatting-only churn. Original author credited viaCo-authored-by. #2784 is left open.Screenshots
No visual change. (The picker now accepts GHES project URLs and projects load on GHES; behavior is exercised by unit tests below since reproducing it needs a real second GHES host + dual
gh auth.)Testing
pnpm lint(oxlint, scoped to changed files — clean)pnpm typecheck(node + web + cli tsgo configs — clean)pnpm test(affected vitest suites — see PR comment for output)pnpm buildNew/updated tests assert that GHES repo targets pass
--hostname ghe.acme.internal+cwd:/repoon discovery, view, field, and slug mutations; thatgitlab.comremotes pass no--hostname; thatgh auth statusruns--hostnamefor the repo's host; thatparseProjectPastecaptures/normalizes GHES hosts, rejects known non-GitHub providers, and rejects the@-spoof; and that same-named owners on github.com/GHES do not share ProjectV2 owner/capability cache probes.AI Review Report
Reviewed the diff adversarially for correctness, edge cases, and cross-platform behavior. Risks checked:
metaKey; host resolution uses git remote parsing only;gh issue close/reopenrouted viacwd(no--hostnameflag) so it works identically on macOS/Linux/Windows. No new platform-conditional code paths.GitHubRepoTargetcarriesconnectionId; when set, the path is read through the SSH git provider and localcwdis omitted (consistent with existingghRepoExecOptions). RPC/IPC both thread the target so environment-runtime callers route correctly.nullfor GitLab/Bitbucket/Azure hosts, so GitLab remotes keep gh's default host (verified by test) rather than being treated as GHES.getGitHubApiHostForRepointogithub-api-host-resolution.tskeptgithub-repository-identity.tsunder the 300-line limit instead of adding amax-linesdisable.Security Audit
execFile(array argv, no shell) — no shell injection surface.--hostnamevalues come only fromnormalizeGitHubApiHost/normalizeProjectUrlHost, which allow[a-z0-9.-]+ optional:port, reject a leading dash (can't be mistaken for a gh flag), and reject@/whitespace (blocks thegithub.com@evil.exampleURL-host spoof). No flag-injection or SSRF-style host smuggling.nullbefore any gh call. ProjectV2 is GitHub-only, so non-GitHub hosts are dropped rather than queried.eval, secret access, network calls, or path traversal. IPC/RPC zod schemas mark both target fields optional and nullable, preserving back-compat for callers with no repo context.Notes
Co-authored-by. Their PR is left open for the maintainer to close.preferredGitHubApiHostis a substring heuristic (includes('github')/includes('ghe')); a configurable known-GHES-hosts list could make routing deterministic for unusually-named enterprise hosts.Made with Orca 🐋