From c60b036ef567905774e03094778e3cf7af6d431a Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 29 Jun 2026 02:03:10 +0300 Subject: [PATCH] perf(github): resolve remote candidates and repo metadata concurrently resolveGitHubPrStatus walked remotes and candidate repos one network call at a time. Resolve all ranked remotes and fetch all candidate repo metadata with Promise.all instead, preserving rank/priority order and dedup. Cuts wall-clock on multi-remote/fork setups so a resolution is far less likely to hit the overall timeout. The PR-search loop keeps its early-return (parallelizing it would issue more calls, not fewer). --- packages/web/server/lib/github/pr-status.js | 33 ++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/web/server/lib/github/pr-status.js b/packages/web/server/lib/github/pr-status.js index f4bfdced..b8341360 100644 --- a/packages/web/server/lib/github/pr-status.js +++ b/packages/web/server/lib/github/pr-status.js @@ -225,21 +225,26 @@ const getRepoMetadata = async (octokit, repo) => { }; const resolveRemoteCandidates = async (directory, rankedRemoteNames) => { + // Resolve every ranked remote concurrently — they're independent git lookups. + // Dedup afterwards in rank order so the result is identical to the previous + // sequential pass, just without paying each lookup's latency back-to-back. + const resolvedRemotes = await Promise.all( + rankedRemoteNames.map((remoteName) => + resolveGitHubRepoFromDirectory(directory, remoteName) + .then((resolved) => ({ remoteName, repo: resolved?.repo || null })) + .catch(() => ({ remoteName, repo: null })), + ), + ); + const results = []; const seenRepoKeys = new Set(); - - for (const remoteName of rankedRemoteNames) { - const resolved = await resolveGitHubRepoFromDirectory(directory, remoteName).catch(() => ({ repo: null })); - const repo = resolved?.repo || null; + for (const { remoteName, repo } of resolvedRemotes) { const repoKey = normalizeRepoKey(repo?.owner, repo?.repo); if (!repo || !repoKey || seenRepoKeys.has(repoKey)) { continue; } seenRepoKeys.add(repoKey); - results.push({ - remoteName, - repo, - }); + results.push({ remoteName, repo }); } return results; @@ -258,8 +263,16 @@ const expandRepoNetwork = async (octokit, candidates) => { expanded.push({ repo, remoteName, priority }); }; - for (const candidate of candidates) { - const metadata = await getRepoMetadata(octokit, candidate.repo); + // Fetch repo metadata for all candidates concurrently (independent GET + // /repos calls), then fold them in candidate order so dedup/priority is + // unchanged from the sequential version. + const metadatas = await Promise.all( + candidates.map((candidate) => + getRepoMetadata(octokit, candidate.repo).then((metadata) => ({ candidate, metadata })), + ), + ); + + for (const { candidate, metadata } of metadatas) { if (!metadata) { continue; }