From a1476d6cb5c303f873270da49b690f3500b50bae Mon Sep 17 00:00:00 2001 From: Brian Ketelsen Date: Wed, 1 Apr 2026 03:16:11 +0000 Subject: [PATCH] perf: parallelize sequential getCommentReactions API calls Replace sequential for...of + await loops with Promise.all() for independent reaction checks on multiple comments. This reduces wall-clock time proportionally to the number of comments. Parallelized locations: - findUnreactedHumanComments in issue-refiner.ts - classifyIssue reaction loop in issue-auditor.ts - Both inline and issue comment reaction checks in getPRReviewComments Co-Authored-By: Claude Opus 4.6 (1M context) --- src/github.ts | 48 +++++++++++++++++++++++++-------------- src/jobs/issue-auditor.ts | 32 +++++++++++++++----------- src/jobs/issue-refiner.ts | 34 ++++++++++++++------------- 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/github.ts b/src/github.ts index bb5e25f..a11e2d0 100644 --- a/src/github.ts +++ b/src/github.ts @@ -1119,17 +1119,21 @@ export async function getPRReviewComments(repo: string, prNumber: number): Promi } } - // Check which inline comments already have a 👍 from Yeti - for (const comment of comments) { - // Check for existing 👍 reaction from Yeti - let hasYetiReaction = false; - try { - const reactionsRaw = await gh(["api", `repos/${repo}/pulls/comments/${comment.id}/reactions`]); - const reactions = JSON.parse(reactionsRaw) as Reaction[]; - hasYetiReaction = reactions.some((r) => r.user.login === selfLogin && r.content === "+1"); - } catch { /* treat as no reaction */ } - if (hasYetiReaction) continue; + // Check which inline comments already have a 👍 from Yeti (parallel) + const inlineReactionChecks = await Promise.all( + comments.map(async (comment) => { + let hasYetiReaction = false; + try { + const reactionsRaw = await gh(["api", `repos/${repo}/pulls/comments/${comment.id}/reactions`]); + const reactions = JSON.parse(reactionsRaw) as Reaction[]; + hasYetiReaction = reactions.some((r) => r.user.login === selfLogin && r.content === "+1"); + } catch { /* treat as no reaction */ } + return { comment, hasYetiReaction }; + }), + ); + for (const { comment, hasYetiReaction } of inlineReactionChecks) { + if (hasYetiReaction) continue; const location = comment.line ? `${comment.path}:${comment.line}` : comment.path; parts.push( `Inline comment by @${comment.user.login} on ${location}:\n` + @@ -1139,6 +1143,8 @@ export async function getPRReviewComments(repo: string, prNumber: number): Promi } // Add non-Yeti, non-bot issue-tab comments without 👍 from Yeti + // Separate comments that need reaction checks from those that don't + const humanIssueComments: typeof issueComments = []; for (const comment of issueComments) { if (!comment.body?.trim()) continue; if (comment.body.trim().toUpperCase() === "LGTM") continue; @@ -1147,15 +1153,23 @@ export async function getPRReviewComments(repo: string, prNumber: number): Promi continue; } if (comment.user.login.endsWith("[bot]")) continue; + humanIssueComments.push(comment); + } - // Check for existing 👍 reaction from Yeti - let hasYetiReaction = false; - try { - const reactions = await getCommentReactions(repo, comment.id); - hasYetiReaction = reactions.some((r) => r.user.login === selfLogin && r.content === "+1"); - } catch { /* treat as no reaction */ } - if (hasYetiReaction) continue; + // Check reactions in parallel for human issue comments + const issueReactionChecks = await Promise.all( + humanIssueComments.map(async (comment) => { + let hasYetiReaction = false; + try { + const reactions = await getCommentReactions(repo, comment.id); + hasYetiReaction = reactions.some((r) => r.user.login === selfLogin && r.content === "+1"); + } catch { /* treat as no reaction */ } + return { comment, hasYetiReaction }; + }), + ); + for (const { comment, hasYetiReaction } of issueReactionChecks) { + if (hasYetiReaction) continue; parts.push(`Comment by @${comment.user.login}:\n${comment.body}`); commentIds.push(comment.id); } diff --git a/src/jobs/issue-auditor.ts b/src/jobs/issue-auditor.ts index cec954d..1af7bdf 100644 --- a/src/jobs/issue-auditor.ts +++ b/src/jobs/issue-auditor.ts @@ -49,20 +49,26 @@ export async function classifyIssue( const selfLogin = await gh.getSelfLogin(); const commentsAfterPlan = comments.slice(lastPlanIdx + 1); - for (const comment of commentsAfterPlan) { - if (gh.isYetiComment(comment.body)) continue; - if (comment.login.endsWith("[bot]")) continue; + const humanComments = commentsAfterPlan.filter( + (comment) => !gh.isYetiComment(comment.body) && !comment.login.endsWith("[bot]"), + ); - try { - const reactions = await gh.getCommentReactions(fullName, comment.id); - const hasReaction = reactions.some( - (r) => r.user.login === selfLogin && r.content === "+1", - ); - if (!hasReaction) return "needs-refinement"; - } catch { - // Treat as unreacted to be safe - return "needs-refinement"; - } + const reactionResults = await Promise.all( + humanComments.map(async (comment) => { + try { + const reactions = await gh.getCommentReactions(fullName, comment.id); + return reactions.some( + (r) => r.user.login === selfLogin && r.content === "+1", + ); + } catch { + // Treat as unreacted to be safe + return false; + } + }), + ); + + if (reactionResults.some((hasReaction) => !hasReaction)) { + return "needs-refinement"; } // Plan has blocking clarifying questions awaiting user response diff --git a/src/jobs/issue-refiner.ts b/src/jobs/issue-refiner.ts index 62a8d19..3e713ff 100644 --- a/src/jobs/issue-refiner.ts +++ b/src/jobs/issue-refiner.ts @@ -485,23 +485,25 @@ async function findUnreactedHumanComments( commentsAfterPlan: gh.IssueComment[], selfLogin: string, ): Promise { - const unreacted: gh.IssueComment[] = []; - for (const comment of commentsAfterPlan) { - if (gh.isYetiComment(comment.body)) continue; - if (comment.login.endsWith("[bot]")) continue; - try { - const reactions = await gh.getCommentReactions(fullName, comment.id); - const hasReaction = reactions.some( - (r) => r.user.login === selfLogin && r.content === "+1", - ); - if (!hasReaction) { - unreacted.push(comment); + const humanComments = commentsAfterPlan.filter( + (comment) => !gh.isYetiComment(comment.body) && !comment.login.endsWith("[bot]"), + ); + + const results = await Promise.all( + humanComments.map(async (comment) => { + try { + const reactions = await gh.getCommentReactions(fullName, comment.id); + const hasReaction = reactions.some( + (r) => r.user.login === selfLogin && r.content === "+1", + ); + return hasReaction ? null : comment; + } catch { + return comment; } - } catch { - unreacted.push(comment); - } - } - return unreacted; + }), + ); + + return results.filter((c): c is gh.IssueComment => c !== null); } export async function run(repos: Repo[]): Promise {