@@ -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 } ,
0 commit comments