Fix GitHub Enterprise host identity loss across PR/work-item paths

- Thread `host` through mobile PR RPC params, IPC work-item lookups, and
  RPC schemas so GHES identity survives the renderer/mobile/main boundary
  instead of silently falling back to a same-named github.com repo.
- Qualify `--repo`/`-R` args for github.com too (not just GHES), since
  gh resolves bare shorthand against a process-level GH_HOST that can
  redirect pinned github.com commands.
- Cache `getOriginGitHubApiRepository` to avoid a per-call uncached
  `git remote get-url` round trip on connection-backed repos.
- Add a local-fork fallback in `getWorkItemDetails` so PRs living on a
  base repo (not visible via the origin slug) still resolve via cwd.
- Centralize the github.com-vs-GHES host predicate in
  `isDefaultGitHubHost` so cache keys, quota scoping, and identity
  checks can't drift out of sync.
This commit is contained in:
Jinjing
2026-07-16 22:32:04 -07:00
parent 5487dff295
commit 8bbbc62f3d
28 changed files with 275 additions and 70 deletions
+47 -4
View File
@@ -1,9 +1,31 @@
import type { GitHubOwnerRepo } from '../../shared/types'
import { isDefaultGitHubHost } from '../../shared/github-repository-identity-key'
import { getOwnerRepo, type LocalGitExecOptions } from './gh-utils'
import { getEnterpriseGitHubRepoSlug } from './github-enterprise-repository'
export type GitHubApiRepository = GitHubOwnerRepo
// Why: the enterprise branch spawns an uncached `git remote get-url` (an SSH
// round trip on connection-backed repos) — hot paths like per-file contents
// and viewed-state toggles resolve per call, so cache like ownerRepoCache does.
const ORIGIN_REPO_CACHE_TTL_MS = 30_000
const originRepoCache = new Map<string, { value: GitHubApiRepository | null; expiresAt: number }>()
const originRepoInFlight = new Map<string, Promise<GitHubApiRepository | null>>()
function originRepoCacheKey(
repoPath: string,
connectionId?: string | null,
localGitOptions: LocalGitExecOptions = {}
): string {
return `${connectionId ?? 'local'}\0${localGitOptions.wslDistro ?? ''}\0${repoPath}`
}
/** @internal - exposed for tests only */
export function _resetOriginGitHubApiRepositoryCache(): void {
originRepoCache.clear()
originRepoInFlight.clear()
}
export async function getOriginGitHubApiRepository(
repoPath: string,
connectionId?: string | null,
@@ -13,9 +35,30 @@ export async function getOriginGitHubApiRepository(
if (ownerRepo) {
return { ...ownerRepo, host: 'github.com' }
}
const enterpriseOptions =
Object.keys(localGitOptions).length > 0 ? { localGitExecOptions: localGitOptions } : {}
return getEnterpriseGitHubRepoSlug(repoPath, connectionId, enterpriseOptions)
const cacheKey = originRepoCacheKey(repoPath, connectionId, localGitOptions)
const cached = originRepoCache.get(cacheKey)
if (cached && cached.expiresAt > Date.now()) {
return cached.value
}
const inFlight = originRepoInFlight.get(cacheKey)
if (inFlight) {
return inFlight
}
const probe = (async () => {
const enterpriseOptions =
Object.keys(localGitOptions).length > 0 ? { localGitExecOptions: localGitOptions } : {}
const slug = await getEnterpriseGitHubRepoSlug(repoPath, connectionId, enterpriseOptions)
originRepoCache.set(cacheKey, { value: slug, expiresAt: Date.now() + ORIGIN_REPO_CACHE_TTL_MS })
return slug
})()
originRepoInFlight.set(cacheKey, probe)
try {
return await probe
} finally {
if (originRepoInFlight.get(cacheKey) === probe) {
originRepoInFlight.delete(cacheKey)
}
}
}
export async function resolveGitHubApiRepository(
@@ -46,7 +89,7 @@ export async function resolveGitHubApiRepository(
}
export function isGitHubDotComRepository(repository: GitHubApiRepository): boolean {
return !repository.host || repository.host.toLowerCase() === 'github.com'
return isDefaultGitHubHost(repository.host)
}
// Why: the gh runner host-qualifies argv from `options.host`, so every known