diff --git a/actions/setup/js/notify_comment_error.cjs b/actions/setup/js/notify_comment_error.cjs index 17f6fe0f062..02fcf63bf38 100644 --- a/actions/setup/js/notify_comment_error.cjs +++ b/actions/setup/js/notify_comment_error.cjs @@ -13,6 +13,7 @@ const { sanitizeContent } = require("./sanitize_content.cjs"); const { ERR_VALIDATION } = require("./error_codes.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { resolveTopLevelDiscussionCommentId } = require("./github_api_helpers.cjs"); +const { assembleMarkdownBodyParts } = require("./markdown_body_helpers.cjs"); /** * Collect generated asset URLs from safe output jobs @@ -258,6 +259,25 @@ async function main() { }); } + // Build the generated footer (attribution + XML marker). Appended after sanitization + // so that the XML traceability marker is not stripped by sanitizeContent. + const workflowSource = process.env.GH_AW_WORKFLOW_SOURCE ?? ""; + const workflowSourceURL = process.env.GH_AW_WORKFLOW_SOURCE_URL ?? ""; + const triggeringIssueNumber = context.payload?.issue?.number; + const triggeringPRNumber = context.payload?.pull_request?.number; + const triggeringDiscussionNumber = context.payload?.discussion?.number; + const markdownParts = assembleMarkdownBodyParts({ + includeFooter: true, + workflowName, + runUrl, + workflowSource, + workflowSourceURL, + triggeringIssueNumber, + triggeringPRNumber, + triggeringDiscussionNumber, + }); + const footer = markdownParts.footer; + // Add "needs-review" label when detection produced a warning if (detectionConclusion === "warning") { await tryAddNeedsReviewLabel(commentRepo); @@ -309,7 +329,7 @@ async function main() { } }`; - const sanitizedMessage = sanitizeContent(message); + const sanitizedMessage = sanitizeContent(message) + "\n\n" + footer; const variables = replyToId ? { dId: discussionId, body: sanitizedMessage, replyToId } : { dId: discussionId, body: sanitizedMessage }; const result = await github.graphql(mutation, variables); const created = result?.addDiscussionComment?.comment; @@ -326,7 +346,7 @@ async function main() { return; } - const sanitizedMessage = sanitizeContent(message); + const sanitizedMessage = sanitizeContent(message) + "\n\n" + footer; const response = await github.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: repoOwner, repo: repoName, @@ -364,7 +384,7 @@ async function main() { // Check if this is a discussion comment (GraphQL node ID format) const isDiscussionComment = commentId.startsWith("DC_"); - const sanitizedMessage = sanitizeContent(message); + const sanitizedMessage = sanitizeContent(message) + "\n\n" + footer; try { if (isDiscussionComment) { diff --git a/actions/setup/js/notify_comment_error.test.cjs b/actions/setup/js/notify_comment_error.test.cjs index 3d376e43671..e9eb3a2b7c0 100644 --- a/actions/setup/js/notify_comment_error.test.cjs +++ b/actions/setup/js/notify_comment_error.test.cjs @@ -331,7 +331,7 @@ const mockCore = { (process.env.GH_AW_SAFE_OUTPUT_JOBS = JSON.stringify({ create_issue: "issue_url" })), await eval(`(async () => { ${notifyCommentScript}; await main(); })()`)); const callArgs = mockGithub.request.mock.calls[0][1]; - expect(callArgs.body).toMatch(/completed successfully!$/); + expect(callArgs.body).toContain("completed successfully!"); }), it("should handle empty safe output jobs gracefully", async () => { ((process.env.GH_AW_COMMENT_ID = "123456"), @@ -340,7 +340,7 @@ const mockCore = { (process.env.GH_AW_AGENT_CONCLUSION = "success"), await eval(`(async () => { ${notifyCommentScript}; await main(); })()`)); const callArgs = mockGithub.request.mock.calls[0][1]; - expect(callArgs.body).toMatch(/completed successfully!$/); + expect(callArgs.body).toContain("completed successfully!"); })); }), describe("when safe_outputs job fails", () => { @@ -373,5 +373,27 @@ const mockCore = { await eval(`(async () => { ${notifyCommentScript}; await main(); })()`), expect(mockGithub.request).toHaveBeenCalledWith("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.objectContaining({ body: expect.stringContaining("completed successfully!") }))); })); + }), + describe("footer in status comment", () => { + (it("should include the generated footer in the updated comment body", async () => { + ((process.env.GH_AW_COMMENT_ID = "123456"), + (process.env.GH_AW_RUN_URL = "https://github.com/owner/repo/actions/runs/123"), + (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), + (process.env.GH_AW_AGENT_CONCLUSION = "success"), + await eval(`(async () => { ${notifyCommentScript}; await main(); })()`)); + const callArgs = mockGithub.request.mock.calls[0][1]; + expect(callArgs.body).toMatch(/Generated by \[test-workflow\]/); + expect(callArgs.body).toMatch(/gh-aw-agentic-workflow/); + }), + it("should include the generated footer even when agent fails", async () => { + ((process.env.GH_AW_COMMENT_ID = "123456"), + (process.env.GH_AW_RUN_URL = "https://github.com/owner/repo/actions/runs/123"), + (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), + (process.env.GH_AW_AGENT_CONCLUSION = "failure"), + await eval(`(async () => { ${notifyCommentScript}; await main(); })()`)); + const callArgs = mockGithub.request.mock.calls[0][1]; + expect(callArgs.body).toMatch(/Generated by \[test-workflow\]/); + expect(callArgs.body).toMatch(/gh-aw-agentic-workflow/); + })); })); }));