Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
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.

pr-auto-merge-trigger.tsBlame108 lines · 1 contributor
21ff9beClaude1/**
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
14import { eq } from "drizzle-orm";
15import { db } from "../db";
16import { prComments, users } from "../db/schema";
17import { combinedStatus } from "./commit-statuses";
18import {
19 computeAutoMergeAction,
20 listAutoMergePrsForRepo,
21 recordEvaluation,
22} from "./pr-auto-merge";
23import { notifyMany } from "./notify";
24import { resolveRef } from "../git/repository";
25
26export 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}