CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
pr-auto-merge-trigger.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 21ff9be | 1 | /** |
| 2 | * Block J16 — Auto-merge trigger called from the commit-status POST path. | |
| 3 | * | |
| 4 | * When a status lands against a commit SHA we find all open auto-merge- | |
| 5 | * enabled PRs in the same repo, resolve each PR's head SHA, and evaluate | |
| 6 | * `computeAutoMergeAction`. If a PR transitions to "ready" we post a | |
| 7 | * one-shot comment + notify the author, then flip `notifiedReady=true` so | |
| 8 | * we don't spam duplicates. | |
| 9 | * | |
| 10 | * Everything here is wrapped in try/catch; a commit-status write never fails | |
| 11 | * because of an auto-merge side-effect. | |
| 12 | */ | |
| 13 | ||
| 14 | import { eq } from "drizzle-orm"; | |
| 15 | import { db } from "../db"; | |
| 16 | import { prComments, users } from "../db/schema"; | |
| 17 | import { combinedStatus } from "./commit-statuses"; | |
| 18 | import { | |
| 19 | computeAutoMergeAction, | |
| 20 | listAutoMergePrsForRepo, | |
| 21 | recordEvaluation, | |
| 22 | } from "./pr-auto-merge"; | |
| 23 | import { notifyMany } from "./notify"; | |
| 24 | import { resolveRef } from "../git/repository"; | |
| 25 | ||
| 26 | export async function attemptAutoMergeForSha(opts: { | |
| 27 | ownerName: string; | |
| 28 | repoName: string; | |
| 29 | repositoryId: string; | |
| 30 | commitSha: string; | |
| 31 | }): Promise<number> { | |
| 32 | const normalised = opts.commitSha.toLowerCase(); | |
| 33 | const prs = await listAutoMergePrsForRepo(opts.repositoryId); | |
| 34 | if (prs.length === 0) return 0; | |
| 35 | ||
| 36 | let triggered = 0; | |
| 37 | for (const pr of prs) { | |
| 38 | if (pr.state !== "open" || pr.isDraft) continue; | |
| 39 | let headSha: string | null = null; | |
| 40 | try { | |
| 41 | headSha = await resolveRef(opts.ownerName, opts.repoName, pr.headBranch); | |
| 42 | } catch { | |
| 43 | headSha = null; | |
| 44 | } | |
| 45 | if (!headSha || headSha.toLowerCase() !== normalised) continue; | |
| 46 | ||
| 47 | let combined: Awaited<ReturnType<typeof combinedStatus>>; | |
| 48 | try { | |
| 49 | combined = await combinedStatus(opts.repositoryId, headSha); | |
| 50 | } catch (err) { | |
| 51 | console.error("[pr-auto-merge-trigger] combinedStatus failed:", err); | |
| 52 | continue; | |
| 53 | } | |
| 54 | ||
| 55 | const action = computeAutoMergeAction({ | |
| 56 | autoMergeEnabled: true, | |
| 57 | prState: pr.state, | |
| 58 | isDraft: pr.isDraft, | |
| 59 | combinedState: combined.state, | |
| 60 | totalChecks: combined.total, | |
| 61 | }); | |
| 62 | ||
| 63 | const { wasAlreadyReady } = await recordEvaluation( | |
| 64 | pr.pullRequestId, | |
| 65 | action, | |
| 66 | combined.state | |
| 67 | ); | |
| 68 | ||
| 69 | if (action.action === "merge" && !wasAlreadyReady) { | |
| 70 | triggered += 1; | |
| 71 | try { | |
| 72 | await db.insert(prComments).values({ | |
| 73 | pullRequestId: pr.pullRequestId, | |
| 74 | authorId: pr.authorId, | |
| 75 | body: [ | |
| 76 | `\u26A1 **Auto-merge ready**`, | |
| 77 | ``, | |
| 78 | `All ${combined.total} commit status check${combined.total === 1 ? "" : "s"} on \`${headSha.slice(0, 7)}\` are now \`success\`.`, | |
| 79 | `Click **Merge pull request** above to complete the merge.`, | |
| 80 | ].join("\n"), | |
| 81 | isAiReview: false, | |
| 82 | }); | |
| 83 | } catch (err) { | |
| 84 | console.error("[pr-auto-merge-trigger] comment failed:", err); | |
| 85 | } | |
| 86 | try { | |
| 87 | // Notify the PR author only — keeps noise low. | |
| 88 | const [authorRow] = await db | |
| 89 | .select({ id: users.id }) | |
| 90 | .from(users) | |
| 91 | .where(eq(users.id, pr.authorId)) | |
| 92 | .limit(1); | |
| 93 | if (authorRow) { | |
| 94 | await notifyMany([authorRow.id], { | |
| 95 | kind: "pr_opened", // no dedicated kind; reuse a benign one | |
| 96 | title: `${opts.ownerName}/${opts.repoName} PR #${pr.number} ready to merge`, | |
| 97 | body: "All commit status checks passed \u2014 auto-merge is ready.", | |
| 98 | url: `/${opts.ownerName}/${opts.repoName}/pulls/${pr.number}`, | |
| 99 | repositoryId: opts.repositoryId, | |
| 100 | }); | |
| 101 | } | |
| 102 | } catch (err) { | |
| 103 | console.error("[pr-auto-merge-trigger] notify failed:", err); | |
| 104 | } | |
| 105 | } | |
| 106 | } | |
| 107 | return triggered; | |
| 108 | } |