From 239dcef1107ad4ec7b866be2f300b8c989543ddb Mon Sep 17 00:00:00 2001 From: Caleb Date: Sun, 28 Jun 2026 15:35:08 +0300 Subject: [PATCH 1/2] fix(checklint): resolve pr number for fork prs via paginated sha lookup --- .../workflow_run.completed/checklint.ts | 161 ++++++++++++------ 1 file changed, 108 insertions(+), 53 deletions(-) diff --git a/src/events/workflow_run.completed/checklint.ts b/src/events/workflow_run.completed/checklint.ts index 0b5d2de..af518a9 100644 --- a/src/events/workflow_run.completed/checklint.ts +++ b/src/events/workflow_run.completed/checklint.ts @@ -1,48 +1,70 @@ import { Context } from "probot"; -export default async (context: Context<"workflow_run.completed">) => { - const lintWorkflows = ["lint", "super-linter", "lint check", "code quality"]; +type WorkflowRunContext = Context<"workflow_run.completed">; +type WorkflowRun = WorkflowRunContext["payload"]["workflow_run"]; - const workflowName = context.payload.workflow_run.name?.toLowerCase() ?? ""; +const LINT_WORKFLOW_NAMES = new Set([ + "lint", + "super-linter", + "lint check", + "code quality", +]); - if (!lintWorkflows.includes(workflowName)) { +const PULLS_PER_PAGE = 100; +const MAX_PAGES = 10; + +export default async (context: WorkflowRunContext): Promise => { + const workflowName = + context.payload.workflow_run.name?.toLowerCase().trim() ?? ""; + + if (!LINT_WORKFLOW_NAMES.has(workflowName)) { return; } const { owner, repo } = context.repo(); const workflowRun = context.payload.workflow_run; - const pull_number = await resolvePullNumber( - context, - workflowRun, + const pullNumber = await resolvePullNumber(context, workflowRun, owner, repo); + if (pullNumber === null) { + context.log.info( + `[lint-bot] Could not resolve a PR for workflow run ${workflowRun.id} ` + + `(branch: ${workflowRun.head_branch ?? "unknown"}, sha: ${workflowRun.head_sha}). Skipping.`, + ); + return; + } + + const body = + workflowRun.conclusion === "success" + ? buildSuccessComment() + : buildFailureComment(owner, repo, workflowRun.id); + + await context.octokit.rest.issues.createComment({ owner, repo, - ); - if (!pull_number) return; - - if (workflowRun.conclusion === "success") { - const body = [ - "> [!NOTE]", - "> Linting checks passed successfully 🎉", - "", - "All formatting and code quality checks are clean.", - "", - "You're good to merge 🚀", - ].join("\n"); - - await context.octokit.rest.issues.createComment({ - owner, - repo, - issue_number: pull_number, - body, - }); + issue_number: pullNumber, + body, + }); +}; - return; - } +function buildSuccessComment(): string { + return [ + "> [!NOTE]", + "> Linting checks passed successfully 🎉", + "", + "All formatting and code quality checks are clean.", + "", + "You're good to merge 🚀", + ].join("\n"); +} - const logsUrl = `https://github.com/${owner}/${repo}/actions/runs/${workflowRun.id}`; +function buildFailureComment( + owner: string, + repo: string, + runId: number, +): string { + const logsUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`; - const body = [ + return [ "> [!WARNING]", "> Linting checks did not pass for this PR.", ">", @@ -65,38 +87,71 @@ export default async (context: Context<"workflow_run.completed">) => { "> This is just a friendly reminder and will **not block** the PR from being merged.", "", ].join("\n"); - - await context.octokit.rest.issues.createComment({ - owner, - repo, - issue_number: pull_number, - body, - }); -}; +} async function resolvePullNumber( - context: Context<"workflow_run.completed">, - workflowRun: Context<"workflow_run.completed">["payload"]["workflow_run"], + context: WorkflowRunContext, + workflowRun: WorkflowRun, owner: string, repo: string, ): Promise { - const directPRs = workflowRun.pull_requests as Array<{ number: number }>; - if (directPRs?.length > 0) { + const headSha: string = workflowRun.head_sha; + const headBranch: string | null = workflowRun.head_branch ?? null; + + const directPRs = workflowRun.pull_requests as Array<{ + number: number; + }> | null; + if (directPRs && directPRs.length > 0) { return directPRs[0].number; } - const headBranch = workflowRun.head_branch; - const headSha = workflowRun.head_sha; + if (!headBranch) { + return null; + } - if (!headBranch) return null; + try { + const { data: samePRs } = await context.octokit.rest.pulls.list({ + owner, + repo, + state: "open", + head: `${owner}:${headBranch}`, + per_page: PULLS_PER_PAGE, + }); - const { data: prs } = await context.octokit.rest.pulls.list({ - owner, - repo, - state: "open", - head: `${owner}:${headBranch}`, - }); + const sameRepoMatch = samePRs.find((pr) => pr.head.sha === headSha); + if (sameRepoMatch) { + return sameRepoMatch.number; + } + } catch (err) { + context.log.warn(`[lint-bot] Same-repo PR lookup failed: ${String(err)}`); + } + + try { + for (let page = 1; page <= MAX_PAGES; page++) { + const { data: prs } = await context.octokit.rest.pulls.list({ + owner, + repo, + state: "open", + per_page: PULLS_PER_PAGE, + page, + }); + + if (prs.length === 0) { + break; + } + + const forkMatch = prs.find((pr) => pr.head.sha === headSha); + if (forkMatch) { + return forkMatch.number; + } + + if (prs.length < PULLS_PER_PAGE) { + break; + } + } + } catch (err) { + context.log.warn(`[lint-bot] Fork PR lookup failed: ${String(err)}`); + } - const match = prs.find((pr) => pr.head.sha === headSha); - return match?.number ?? null; + return null; } From e5ec4c3b199a9d6764ea9feb3d312afff0b3fcf6 Mon Sep 17 00:00:00 2001 From: Caleb Date: Sun, 28 Jun 2026 15:50:54 +0300 Subject: [PATCH 2/2] fix: simplify fork pr lookup to a single unpaginated call --- .../workflow_run.completed/checklint.ts | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/src/events/workflow_run.completed/checklint.ts b/src/events/workflow_run.completed/checklint.ts index af518a9..8143517 100644 --- a/src/events/workflow_run.completed/checklint.ts +++ b/src/events/workflow_run.completed/checklint.ts @@ -10,9 +10,6 @@ const LINT_WORKFLOW_NAMES = new Set([ "code quality", ]); -const PULLS_PER_PAGE = 100; -const MAX_PAGES = 10; - export default async (context: WorkflowRunContext): Promise => { const workflowName = context.payload.workflow_run.name?.toLowerCase().trim() ?? ""; @@ -115,7 +112,6 @@ async function resolvePullNumber( repo, state: "open", head: `${owner}:${headBranch}`, - per_page: PULLS_PER_PAGE, }); const sameRepoMatch = samePRs.find((pr) => pr.head.sha === headSha); @@ -127,28 +123,15 @@ async function resolvePullNumber( } try { - for (let page = 1; page <= MAX_PAGES; page++) { - const { data: prs } = await context.octokit.rest.pulls.list({ - owner, - repo, - state: "open", - per_page: PULLS_PER_PAGE, - page, - }); - - if (prs.length === 0) { - break; - } - - const forkMatch = prs.find((pr) => pr.head.sha === headSha); - if (forkMatch) { - return forkMatch.number; - } - - if (prs.length < PULLS_PER_PAGE) { - break; - } - } + const { data: prs } = await context.octokit.rest.pulls.list({ + owner, + repo, + state: "open", + per_page: 30, + }); + + const forkMatch = prs.find((pr) => pr.head.sha === headSha); + return forkMatch?.number ?? null; } catch (err) { context.log.warn(`[lint-bot] Fork PR lookup failed: ${String(err)}`); }