Skip to content

Commit a091a27

Browse files
committed
fix(worker): comment on the head an autofix run published, not the trigger sha
1 parent 90845cc commit a091a27

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

apps/worker/src/workflows/blocks/post-pr-comment.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,86 @@ describe("post_pr_comment execute", () => {
253253
expect(result.kind).toBe("next");
254254
});
255255

256+
function finalizedWithoutPr(pushedHead: string): WorkspacePublicationResult {
257+
// What a remediation graph really produces: the branch was finalized, but no
258+
// pull request was opened because the pull request already existed.
259+
return {
260+
status: "finalized",
261+
prs: [],
262+
repositories: [
263+
{
264+
provider: "github",
265+
repoPath: "acme/api",
266+
branchName: "blazebot/awt-1",
267+
defaultBranch: "main",
268+
expectedHead: "abc123",
269+
pushedHead,
270+
},
271+
],
272+
};
273+
}
274+
275+
function prTriggerEntry() {
276+
return {
277+
kind: "pr_trigger" as const,
278+
triggerType: "trigger_pr_checks_failed" as const,
279+
subjectKey: "ticket:jira:AWT-1",
280+
ticketKey: "AWT-1",
281+
ownerToken: "owner:test",
282+
definitionId: 1,
283+
definitionVersion: 1,
284+
scope: "workflow_owned" as const,
285+
pr: makePrPayload(),
286+
};
287+
}
288+
289+
it("comments on the head this run published, not the sha the trigger recorded", async () => {
290+
const postPRComment = vi.fn().mockResolvedValue({ url: "https://pr/comment" });
291+
mocks.createRepositoryVCS.mockReturnValue({
292+
getPRHead: vi
293+
.fn()
294+
.mockResolvedValue({ headSha: "pushed-by-this-run", baseRef: "main", state: "open" }),
295+
postPRComment,
296+
});
297+
298+
const result = await execute(
299+
makeNode("post_pr_comment", { body: "Automated fix pushed." }),
300+
{},
301+
makeCtx({
302+
entry: prTriggerEntry(),
303+
publication: finalizedWithoutPr("pushed-by-this-run"),
304+
}),
305+
);
306+
307+
expect(postPRComment).toHaveBeenCalledWith(7, marked("Automated fix pushed."));
308+
expect(result.kind).toBe("next");
309+
});
310+
311+
it("still refuses to comment when somebody else moved the head", async () => {
312+
const postPRComment = vi.fn().mockResolvedValue({ url: "https://pr/comment" });
313+
mocks.createRepositoryVCS.mockReturnValue({
314+
getPRHead: vi
315+
.fn()
316+
.mockResolvedValue({ headSha: "someone-else", baseRef: "main", state: "open" }),
317+
postPRComment,
318+
});
319+
320+
const result = await execute(
321+
makeNode("post_pr_comment", { body: "Automated fix pushed." }),
322+
{},
323+
makeCtx({
324+
entry: prTriggerEntry(),
325+
publication: finalizedWithoutPr("pushed-by-this-run"),
326+
}),
327+
);
328+
329+
expect(postPRComment).not.toHaveBeenCalled();
330+
expect(result).toMatchObject({
331+
kind: "execution_error",
332+
error: expect.objectContaining({ message: expect.stringContaining("stale PR/MR head") }),
333+
});
334+
});
335+
256336
it.each([
257337
{
258338
current: { headSha: "new-head", baseRef: "main", state: "open" as const },

apps/worker/src/workflows/blocks/post-pr-comment.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,28 @@ export const execute: BlockExecuteFn = async (
166166
});
167167
}
168168
} else if (ctx.entry.kind === "pr_trigger") {
169+
// Publication reports `prs` only when it opened one, so a graph that
170+
// remediates an existing pull request always lands here rather than in the
171+
// branch above. Its head is still this run's own work: a fix agent commits
172+
// AND pushes from inside the sandbox, which is what makes CI re-run. Taking
173+
// the sha the trigger recorded would compare the comment against a head this
174+
// same run superseded and refuse to post on every successful fix. Publication
175+
// already proved that head is ours, so prefer what it finalized, and keep the
176+
// trigger sha for a run that published nothing. A foreign push still fails
177+
// the comparison below and still stops the comment.
178+
const finalized = ctx.publication?.repositories.find(
179+
(repository) =>
180+
ctx.entry.kind === "pr_trigger" &&
181+
repository.provider === ctx.entry.pr.provider &&
182+
repository.repoPath === ctx.entry.pr.repoPath,
183+
);
169184
prs = [
170185
{
171186
provider: ctx.entry.pr.provider,
172187
repoPath: ctx.entry.pr.repoPath,
173188
baseBranch: ctx.entry.pr.baseRef,
174189
prId: ctx.entry.pr.prNumber,
175-
expectedHead: ctx.entry.pr.headSha,
190+
expectedHead: finalized?.pushedHead ?? ctx.entry.pr.headSha,
176191
expectedState: ctx.entry.triggerType === "trigger_pr_merged" ? "merged" : "open",
177192
},
178193
];

0 commit comments

Comments
 (0)