From 422ce9bb0115107535f91428f1e48ab244ac6410 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:30:19 +0000 Subject: [PATCH 1/8] Initial plan From 8e22d15d153f15baf539301f6af5fed6a7f8427d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:43:06 +0000 Subject: [PATCH 2/8] feat: add activation-comments option to disable activation/fallback comments Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_workflow_run_comment.cjs | 8 ++++++++ actions/setup/js/messages_core.cjs | 1 + actions/setup/js/notify_comment_error.cjs | 6 ++++++ actions/setup/js/update_activation_comment.cjs | 6 ++++++ pkg/parser/schemas/main_workflow_schema.json | 5 +++++ pkg/workflow/compiler_types.go | 1 + pkg/workflow/safe_outputs_config_messages.go | 7 +++++++ 7 files changed, 34 insertions(+) diff --git a/actions/setup/js/add_workflow_run_comment.cjs b/actions/setup/js/add_workflow_run_comment.cjs index 8cc5508ab24..50cdb2bb95f 100644 --- a/actions/setup/js/add_workflow_run_comment.cjs +++ b/actions/setup/js/add_workflow_run_comment.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs"); +const { getMessages } = require("./messages_core.cjs"); /** * Event type descriptions for comment messages @@ -60,6 +61,13 @@ function setCommentOutputs(commentId, commentUrl) { * Use add_reaction.cjs in the pre-activation job to add reactions first for immediate feedback. */ async function main() { + // Check if activation comments are disabled + const messagesConfig = getMessages(); + if (messagesConfig?.activationComments === false) { + core.info("activation-comments is disabled: skipping activation comment creation"); + return; + } + const runId = context.runId; const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 7db11d43ccc..9238b617be7 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -37,6 +37,7 @@ * @property {string} [agentFailureComment] - Custom footer template for comments on agent failure tracking issues * @property {string} [closeOlderDiscussion] - Custom message for closing older discussions as outdated * @property {boolean} [appendOnlyComments] - If true, create new comments instead of updating the activation comment + * @property {boolean} [activationComments] - If false, disable all activation/fallback comments entirely (default: true) */ /** diff --git a/actions/setup/js/notify_comment_error.cjs b/actions/setup/js/notify_comment_error.cjs index 8a416200cb5..4f56f23634c 100644 --- a/actions/setup/js/notify_comment_error.cjs +++ b/actions/setup/js/notify_comment_error.cjs @@ -60,6 +60,12 @@ async function main() { const messagesConfig = getMessages(); const appendOnlyComments = messagesConfig?.appendOnlyComments === true; + // If activation comments are disabled entirely, skip all comment updates + if (messagesConfig?.activationComments === false) { + core.info("activation-comments is disabled: skipping completion comment update"); + return; + } + core.info(`Comment ID: ${commentId}`); core.info(`Comment Repo: ${commentRepo}`); core.info(`Run URL: ${runUrl}`); diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index 18fe6ae5ea3..c5311d72e03 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -54,6 +54,12 @@ async function updateActivationCommentWithMessage(github, context, core, message const messagesConfig = getMessages(); const appendOnlyComments = messagesConfig?.appendOnlyComments === true; + // Check if activation comments are disabled entirely + if (messagesConfig?.activationComments === false) { + core.info("activation-comments is disabled: skipping activation comment update"); + return; + } + // Parse comment repo (format: "owner/repo") with validation let repoOwner = context.repo.owner; let repoName = context.repo.repo; diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 86a40e8e1cf..f83a1e55a3f 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -6772,6 +6772,11 @@ "type": "boolean", "description": "When enabled, workflow completion notifier creates a new comment instead of editing the activation comment. Creates an append-only timeline of workflow runs. Default: false", "default": false + }, + "activation-comments": { + "type": "boolean", + "description": "When set to false, disables all activation and fallback comments entirely (run-started, run-success, run-failure, PR/issue creation links). Useful for silent automation workflows that create PRs or issues without posting informational bot comments. Default: true", + "default": true } }, "additionalProperties": false diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 91b26513409..3c827b82a00 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -537,6 +537,7 @@ type SafeOutputMessagesConfig struct { StagedTitle string `yaml:"staged-title,omitempty" json:"stagedTitle,omitempty"` // Custom styled mode title template StagedDescription string `yaml:"staged-description,omitempty" json:"stagedDescription,omitempty"` // Custom staged mode description template AppendOnlyComments bool `yaml:"append-only-comments,omitempty" json:"appendOnlyComments,omitempty"` // If true, post run status as new comments instead of updating the activation comment + ActivationComments *bool `yaml:"activation-comments,omitempty" json:"activationComments,omitempty"` // If false, disable all activation/fallback comments entirely. When nil (unset) or true, comments are enabled (default behavior) RunStarted string `yaml:"run-started,omitempty" json:"runStarted,omitempty"` // Custom workflow activation message template RunSuccess string `yaml:"run-success,omitempty" json:"runSuccess,omitempty"` // Custom workflow success message template RunFailure string `yaml:"run-failure,omitempty" json:"runFailure,omitempty"` // Custom workflow failure message template diff --git a/pkg/workflow/safe_outputs_config_messages.go b/pkg/workflow/safe_outputs_config_messages.go index ff7ec487e93..5b691b51454 100644 --- a/pkg/workflow/safe_outputs_config_messages.go +++ b/pkg/workflow/safe_outputs_config_messages.go @@ -34,6 +34,13 @@ func parseMessagesConfig(messagesMap map[string]any) *SafeOutputMessagesConfig { } } + if activationComments, exists := messagesMap["activation-comments"]; exists { + if enabled, ok := activationComments.(bool); ok { + config.ActivationComments = &enabled + safeOutputMessagesLog.Printf("Set activation-comments: %t", enabled) + } + } + setStringFromMap(messagesMap, "footer", &config.Footer) setStringFromMap(messagesMap, "footer-install", &config.FooterInstall) setStringFromMap(messagesMap, "footer-workflow-recompile", &config.FooterWorkflowRecompile) From 8a36a07c9acb05c853dd5c3f6289789653e0e5cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:55:56 +0000 Subject: [PATCH 3/8] feat: templateable activation messages, remove emoji, add run ID footer Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/messages_core.cjs | 3 + actions/setup/js/messages_run_status.cjs | 58 +++++++++++++++++++ .../setup/js/update_activation_comment.cjs | 23 +++++++- .../js/update_activation_comment.test.cjs | 46 ++++++++++++--- pkg/parser/schemas/main_workflow_schema.json | 15 +++++ pkg/workflow/compiler_types.go | 3 + pkg/workflow/safe_outputs_config_messages.go | 3 + 7 files changed, 141 insertions(+), 10 deletions(-) diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 9238b617be7..6e7bfabe730 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -33,6 +33,9 @@ * @property {string} [runSuccess] - Custom workflow success message template * @property {string} [runFailure] - Custom workflow failure message template * @property {string} [detectionFailure] - Custom detection job failure message template + * @property {string} [pullRequestCreated] - Custom template for pull request creation link. Placeholders: {item_number}, {item_url} + * @property {string} [issueCreated] - Custom template for issue creation link. Placeholders: {item_number}, {item_url} + * @property {string} [commitPushed] - Custom template for commit push link. Placeholders: {commit_sha}, {short_sha}, {commit_url} * @property {string} [agentFailureIssue] - Custom footer template for agent failure tracking issues * @property {string} [agentFailureComment] - Custom footer template for comments on agent failure tracking issues * @property {string} [closeOlderDiscussion] - Custom message for closing older discussions as outdated diff --git a/actions/setup/js/messages_run_status.cjs b/actions/setup/js/messages_run_status.cjs index eb6f7ee614a..88c95236409 100644 --- a/actions/setup/js/messages_run_status.cjs +++ b/actions/setup/js/messages_run_status.cjs @@ -108,9 +108,67 @@ function getDetectionFailureMessage(ctx) { return messages?.detectionFailure ? renderTemplate(messages.detectionFailure, templateContext) : renderTemplate(defaultMessage, templateContext); } +/** + * @typedef {Object} PullRequestCreatedContext + * @property {number} itemNumber - PR number + * @property {string} itemUrl - URL of the pull request + */ + +/** + * Get the pull-request-created message, using custom template if configured. + * @param {PullRequestCreatedContext} ctx - Context for message generation + * @returns {string} Pull-request-created message + */ +function getPullRequestCreatedMessage(ctx) { + const messages = getMessages(); + const templateContext = toSnakeCase(ctx); + const defaultMessage = "Pull request created: [#{item_number}]({item_url})"; + return messages?.pullRequestCreated ? renderTemplate(messages.pullRequestCreated, templateContext) : renderTemplate(defaultMessage, templateContext); +} + +/** + * @typedef {Object} IssueCreatedContext + * @property {number} itemNumber - Issue number + * @property {string} itemUrl - URL of the issue + */ + +/** + * Get the issue-created message, using custom template if configured. + * @param {IssueCreatedContext} ctx - Context for message generation + * @returns {string} Issue-created message + */ +function getIssueCreatedMessage(ctx) { + const messages = getMessages(); + const templateContext = toSnakeCase(ctx); + const defaultMessage = "Issue created: [#{item_number}]({item_url})"; + return messages?.issueCreated ? renderTemplate(messages.issueCreated, templateContext) : renderTemplate(defaultMessage, templateContext); +} + +/** + * @typedef {Object} CommitPushedContext + * @property {string} commitSha - Full commit SHA + * @property {string} shortSha - Short (7-char) commit SHA + * @property {string} commitUrl - URL of the commit + */ + +/** + * Get the commit-pushed message, using custom template if configured. + * @param {CommitPushedContext} ctx - Context for message generation + * @returns {string} Commit-pushed message + */ +function getCommitPushedMessage(ctx) { + const messages = getMessages(); + const templateContext = toSnakeCase(ctx); + const defaultMessage = "Commit pushed: [`{short_sha}`]({commit_url})"; + return messages?.commitPushed ? renderTemplate(messages.commitPushed, templateContext) : renderTemplate(defaultMessage, templateContext); +} + module.exports = { getRunStartedMessage, getRunSuccessMessage, getRunFailureMessage, getDetectionFailureMessage, + getPullRequestCreatedMessage, + getIssueCreatedMessage, + getCommitPushedMessage, }; diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index c5311d72e03..c73800ee115 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -4,6 +4,16 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { getMessages } = require("./messages_core.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); +const { getPullRequestCreatedMessage, getIssueCreatedMessage, getCommitPushedMessage } = require("./messages_run_status.cjs"); + +/** + * Get the current workflow run ID from context or environment. + * @param {any} context - GitHub Actions context + * @returns {string} The run ID, or empty string if not available + */ +function getRunId(context) { + return String(context.runId || process.env.GITHUB_RUN_ID || ""); +} /** * Update the activation comment with a link to the created pull request or issue @@ -16,7 +26,12 @@ const { sanitizeContent } = require("./sanitize_content.cjs"); */ async function updateActivationComment(github, context, core, itemUrl, itemNumber, itemType = "pull_request") { const itemLabel = itemType === "issue" ? "issue" : "pull request"; - const linkMessage = itemType === "issue" ? `\n\n✅ Issue created: [#${itemNumber}](${itemUrl})` : `\n\n✅ Pull request created: [#${itemNumber}](${itemUrl})`; + const runId = getRunId(context); + const body = itemType === "issue" ? getIssueCreatedMessage({ itemNumber, itemUrl }) : getPullRequestCreatedMessage({ itemNumber, itemUrl }); + let linkMessage = `\n\n${body}`; + if (runId) { + linkMessage += `\n\n`; + } await updateActivationCommentWithMessage(github, context, core, linkMessage, itemLabel, { targetIssueNumber: itemNumber }); } @@ -32,7 +47,11 @@ async function updateActivationComment(github, context, core, itemUrl, itemNumbe */ async function updateActivationCommentWithCommit(github, context, core, commitSha, commitUrl, options = {}) { const shortSha = commitSha.substring(0, 7); - const message = `\n\n✅ Commit pushed: [\`${shortSha}\`](${commitUrl})`; + const runId = getRunId(context); + let message = `\n\n${getCommitPushedMessage({ commitSha, shortSha, commitUrl })}`; + if (runId) { + message += `\n\n`; + } await updateActivationCommentWithMessage(github, context, core, message, "commit", options); } diff --git a/actions/setup/js/update_activation_comment.test.cjs b/actions/setup/js/update_activation_comment.test.cjs index 47f7a1e58bc..6d1f253174f 100644 --- a/actions/setup/js/update_activation_comment.test.cjs +++ b/actions/setup/js/update_activation_comment.test.cjs @@ -28,6 +28,36 @@ const createTestableFunction = scriptContent => { // Mock sanitizeContent to return input as-is for testing return { sanitizeContent: content => content }; } + if (module === "./messages_run_status.cjs") { + const messagesEnv = process.env.GH_AW_SAFE_OUTPUT_MESSAGES; + const messages = messagesEnv ? JSON.parse(messagesEnv) : null; + const renderTmpl = (tmpl, ctx) => tmpl.replace(/\{(\w+)\}/g, (m, k) => (ctx[k] !== undefined ? String(ctx[k]) : m)); + const toSnake = obj => { + const r = {}; + for (const [k, v] of Object.entries(obj)) { + r[k.replace(/([A-Z])/g, "_$1").toLowerCase()] = v; + r[k] = v; + } + return r; + }; + return { + getPullRequestCreatedMessage: ctx => { + const tc = toSnake(ctx); + const def = "Pull request created: [#{item_number}]({item_url})"; + return messages?.pullRequestCreated ? renderTmpl(messages.pullRequestCreated, tc) : renderTmpl(def, tc); + }, + getIssueCreatedMessage: ctx => { + const tc = toSnake(ctx); + const def = "Issue created: [#{item_number}]({item_url})"; + return messages?.issueCreated ? renderTmpl(messages.issueCreated, tc) : renderTmpl(def, tc); + }, + getCommitPushedMessage: ctx => { + const tc = toSnake(ctx); + const def = "Commit pushed: [`{short_sha}`]({commit_url})"; + return messages?.commitPushed ? renderTmpl(messages.commitPushed, tc) : renderTmpl(def, tc); + }, + }; + } throw new Error(`Module ${module} not mocked in test`); }; return new Function( @@ -56,7 +86,7 @@ describe("update_activation_comment.cjs", () => { owner: "testowner", repo: "testrepo", issue_number: 42, - body: expect.stringContaining("✅ Pull request created: [#42]"), + body: expect.stringContaining("Pull request created: [#42]"), }) ); expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully created comment with pull request link on #42"); @@ -83,7 +113,7 @@ describe("update_activation_comment.cjs", () => { expect(mockDependencies.github.request).toHaveBeenCalledWith("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.objectContaining({ owner: "testowner", repo: "testrepo", comment_id: 123456 })), expect(mockDependencies.github.request).toHaveBeenCalledWith( "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", - expect.objectContaining({ owner: "testowner", repo: "testrepo", comment_id: 123456, body: expect.stringContaining("✅ Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)") }) + expect.objectContaining({ owner: "testowner", repo: "testrepo", comment_id: 123456, body: expect.stringContaining("Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)") }) ), expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated comment with pull request link")); }), @@ -102,7 +132,7 @@ describe("update_activation_comment.cjs", () => { expect(mockDependencies.github.graphql).toHaveBeenCalledWith(expect.stringContaining("query($commentId: ID!)"), expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF" })), expect(mockDependencies.github.graphql).toHaveBeenCalledWith( expect.stringContaining("mutation($commentId: ID!, $body: String!)"), - expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF", body: expect.stringContaining("✅ Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)") }) + expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF", body: expect.stringContaining("Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)") }) ), expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated discussion comment with pull request link")); }), @@ -175,7 +205,7 @@ describe("update_activation_comment.cjs", () => { (await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/issues/99", 99, "issue"), expect(mockDependencies.github.request).toHaveBeenCalledWith( "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", - expect.objectContaining({ owner: "testowner", repo: "testrepo", comment_id: 123456, body: expect.stringContaining("✅ Issue created: [#99](https://github.com/testowner/testrepo/issues/99)") }) + expect.objectContaining({ owner: "testowner", repo: "testrepo", comment_id: 123456, body: expect.stringContaining("Issue created: [#99](https://github.com/testowner/testrepo/issues/99)") }) ), expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated comment with issue link")); }), @@ -193,7 +223,7 @@ describe("update_activation_comment.cjs", () => { (await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/issues/99", 99, "issue"), expect(mockDependencies.github.graphql).toHaveBeenCalledWith( expect.stringContaining("mutation($commentId: ID!, $body: String!)"), - expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF", body: expect.stringContaining("✅ Issue created: [#99](https://github.com/testowner/testrepo/issues/99)") }) + expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF", body: expect.stringContaining("Issue created: [#99](https://github.com/testowner/testrepo/issues/99)") }) ), expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated discussion comment with issue link")); }), @@ -219,7 +249,7 @@ describe("update_activation_comment.cjs", () => { owner: "testowner", repo: "testrepo", issue_number: 225, - body: expect.stringContaining("✅ Commit pushed:"), + body: expect.stringContaining("Commit pushed:"), }) ); expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully created comment with commit link on #225"); @@ -307,7 +337,7 @@ describe("update_activation_comment.cjs", () => { owner: "testowner", repo: "testrepo", issue_number: 42, - body: expect.stringContaining("✅ Pull request created: [#99]"), + body: expect.stringContaining("Pull request created: [#99]"), }) ); expect(mockDependencies.core.info).toHaveBeenCalledWith("Append-only-comments enabled: creating a new comment"); @@ -358,7 +388,7 @@ describe("update_activation_comment.cjs", () => { expect.stringContaining("mutation"), expect.objectContaining({ dId: "D_kwDOABCDEF4ABCDEF", - body: expect.stringContaining("✅ Pull request created: [#99]"), + body: expect.stringContaining("Pull request created: [#99]"), }) ); expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully created append-only discussion comment with pull request link"); diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index f83a1e55a3f..011350bf50c 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -6768,6 +6768,21 @@ "description": "Custom footer template for comments on agent failure tracking issues. Available placeholders: {workflow_name}, {run_url}. Default: '> Agent failure update from [{workflow_name}]({run_url})'", "examples": ["> Agent failure update from [{workflow_name}]({run_url})", "> Update from [{workflow_name}]({run_url})"] }, + "pull-request-created": { + "type": "string", + "description": "Custom message template for pull request creation link appended to the activation comment. Available placeholders: {item_number}, {item_url}. Default: 'Pull request created: [#{item_number}]({item_url})'", + "examples": ["Pull request created: [#{item_number}]({item_url})", "[#{item_number}]({item_url}) opened"] + }, + "issue-created": { + "type": "string", + "description": "Custom message template for issue creation link appended to the activation comment. Available placeholders: {item_number}, {item_url}. Default: 'Issue created: [#{item_number}]({item_url})'", + "examples": ["Issue created: [#{item_number}]({item_url})", "[#{item_number}]({item_url}) filed"] + }, + "commit-pushed": { + "type": "string", + "description": "Custom message template for commit push link appended to the activation comment. Available placeholders: {commit_sha}, {short_sha}, {commit_url}. Default: 'Commit pushed: [`{short_sha}`]({commit_url})'", + "examples": ["Commit pushed: [`{short_sha}`]({commit_url})", "[`{short_sha}`]({commit_url}) pushed"] + }, "append-only-comments": { "type": "boolean", "description": "When enabled, workflow completion notifier creates a new comment instead of editing the activation comment. Creates an append-only timeline of workflow runs. Default: false", diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 3c827b82a00..5bf18f3f1ae 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -542,6 +542,9 @@ type SafeOutputMessagesConfig struct { RunSuccess string `yaml:"run-success,omitempty" json:"runSuccess,omitempty"` // Custom workflow success message template RunFailure string `yaml:"run-failure,omitempty" json:"runFailure,omitempty"` // Custom workflow failure message template DetectionFailure string `yaml:"detection-failure,omitempty" json:"detectionFailure,omitempty"` // Custom detection job failure message template + PullRequestCreated string `yaml:"pull-request-created,omitempty" json:"pullRequestCreated,omitempty"` // Custom message template for pull request creation link. Placeholders: {item_number}, {item_url} + IssueCreated string `yaml:"issue-created,omitempty" json:"issueCreated,omitempty"` // Custom message template for issue creation link. Placeholders: {item_number}, {item_url} + CommitPushed string `yaml:"commit-pushed,omitempty" json:"commitPushed,omitempty"` // Custom message template for commit push link. Placeholders: {commit_sha}, {short_sha}, {commit_url} AgentFailureIssue string `yaml:"agent-failure-issue,omitempty" json:"agentFailureIssue,omitempty"` // Custom footer template for agent failure tracking issues AgentFailureComment string `yaml:"agent-failure-comment,omitempty" json:"agentFailureComment,omitempty"` // Custom footer template for comments on agent failure tracking issues } diff --git a/pkg/workflow/safe_outputs_config_messages.go b/pkg/workflow/safe_outputs_config_messages.go index 5b691b51454..8543bb6ea7a 100644 --- a/pkg/workflow/safe_outputs_config_messages.go +++ b/pkg/workflow/safe_outputs_config_messages.go @@ -51,6 +51,9 @@ func parseMessagesConfig(messagesMap map[string]any) *SafeOutputMessagesConfig { setStringFromMap(messagesMap, "run-success", &config.RunSuccess) setStringFromMap(messagesMap, "run-failure", &config.RunFailure) setStringFromMap(messagesMap, "detection-failure", &config.DetectionFailure) + setStringFromMap(messagesMap, "pull-request-created", &config.PullRequestCreated) + setStringFromMap(messagesMap, "issue-created", &config.IssueCreated) + setStringFromMap(messagesMap, "commit-pushed", &config.CommitPushed) setStringFromMap(messagesMap, "agent-failure-issue", &config.AgentFailureIssue) setStringFromMap(messagesMap, "agent-failure-comment", &config.AgentFailureComment) From a4f7607400cf47bfbba0ce443feed224f2f526d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 06:08:01 +0000 Subject: [PATCH 4/8] refactor: use templatable boolean for activation-comments field Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_workflow_run_comment.cjs | 3 +- actions/setup/js/messages_core.cjs | 2 +- actions/setup/js/notify_comment_error.cjs | 3 +- .../setup/js/update_activation_comment.cjs | 3 +- .../js/update_activation_comment.test.cjs | 8 +++++ pkg/parser/schemas/main_workflow_schema.json | 4 +-- pkg/workflow/compiler_types.go | 2 +- pkg/workflow/imports.go | 12 ++++++++ pkg/workflow/safe_outputs_config_messages.go | 8 ++--- pkg/workflow/safe_outputs_test.go | 30 +++++++++++++++++++ 10 files changed, 63 insertions(+), 12 deletions(-) diff --git a/actions/setup/js/add_workflow_run_comment.cjs b/actions/setup/js/add_workflow_run_comment.cjs index 50cdb2bb95f..a09165c0684 100644 --- a/actions/setup/js/add_workflow_run_comment.cjs +++ b/actions/setup/js/add_workflow_run_comment.cjs @@ -7,6 +7,7 @@ const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs"); const { getMessages } = require("./messages_core.cjs"); +const { parseBoolTemplatable } = require("./templatable.cjs"); /** * Event type descriptions for comment messages @@ -63,7 +64,7 @@ function setCommentOutputs(commentId, commentUrl) { async function main() { // Check if activation comments are disabled const messagesConfig = getMessages(); - if (messagesConfig?.activationComments === false) { + if (!parseBoolTemplatable(messagesConfig?.activationComments, true)) { core.info("activation-comments is disabled: skipping activation comment creation"); return; } diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 6e7bfabe730..2150e9fd9dc 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -40,7 +40,7 @@ * @property {string} [agentFailureComment] - Custom footer template for comments on agent failure tracking issues * @property {string} [closeOlderDiscussion] - Custom message for closing older discussions as outdated * @property {boolean} [appendOnlyComments] - If true, create new comments instead of updating the activation comment - * @property {boolean} [activationComments] - If false, disable all activation/fallback comments entirely (default: true) + * @property {string|boolean} [activationComments] - If false or "false", disable all activation/fallback comments entirely. Supports templatable boolean values (default: true) */ /** diff --git a/actions/setup/js/notify_comment_error.cjs b/actions/setup/js/notify_comment_error.cjs index 4f56f23634c..fe2792e2ee3 100644 --- a/actions/setup/js/notify_comment_error.cjs +++ b/actions/setup/js/notify_comment_error.cjs @@ -11,6 +11,7 @@ const { getMessages } = require("./messages_core.cjs"); const { getErrorMessage, isLockedError } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { ERR_VALIDATION } = require("./error_codes.cjs"); +const { parseBoolTemplatable } = require("./templatable.cjs"); /** * Collect generated asset URLs from safe output jobs @@ -61,7 +62,7 @@ async function main() { const appendOnlyComments = messagesConfig?.appendOnlyComments === true; // If activation comments are disabled entirely, skip all comment updates - if (messagesConfig?.activationComments === false) { + if (!parseBoolTemplatable(messagesConfig?.activationComments, true)) { core.info("activation-comments is disabled: skipping completion comment update"); return; } diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index c73800ee115..83dc770dfdb 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -5,6 +5,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { getMessages } = require("./messages_core.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getPullRequestCreatedMessage, getIssueCreatedMessage, getCommitPushedMessage } = require("./messages_run_status.cjs"); +const { parseBoolTemplatable } = require("./templatable.cjs"); /** * Get the current workflow run ID from context or environment. @@ -74,7 +75,7 @@ async function updateActivationCommentWithMessage(github, context, core, message const appendOnlyComments = messagesConfig?.appendOnlyComments === true; // Check if activation comments are disabled entirely - if (messagesConfig?.activationComments === false) { + if (!parseBoolTemplatable(messagesConfig?.activationComments, true)) { core.info("activation-comments is disabled: skipping activation comment update"); return; } diff --git a/actions/setup/js/update_activation_comment.test.cjs b/actions/setup/js/update_activation_comment.test.cjs index 6d1f253174f..6370d10164a 100644 --- a/actions/setup/js/update_activation_comment.test.cjs +++ b/actions/setup/js/update_activation_comment.test.cjs @@ -58,6 +58,14 @@ const createTestableFunction = scriptContent => { }, }; } + if (module === "./templatable.cjs") { + return { + parseBoolTemplatable: (value, defaultValue = true) => { + if (value === undefined || value === null) return defaultValue; + return String(value) !== "false"; + }, + }; + } throw new Error(`Module ${module} not mocked in test`); }; return new Function( diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 011350bf50c..0792880c2ab 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -6789,8 +6789,8 @@ "default": false }, "activation-comments": { - "type": "boolean", - "description": "When set to false, disables all activation and fallback comments entirely (run-started, run-success, run-failure, PR/issue creation links). Useful for silent automation workflows that create PRs or issues without posting informational bot comments. Default: true", + "type": ["boolean", "string"], + "description": "When set to false or \"false\", disables all activation and fallback comments entirely (run-started, run-success, run-failure, PR/issue creation links). Supports templatable boolean values including GitHub Actions expressions (e.g. ${{ inputs.activation-comments }}). Default: true", "default": true } }, diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 5bf18f3f1ae..9be1cec4640 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -537,7 +537,7 @@ type SafeOutputMessagesConfig struct { StagedTitle string `yaml:"staged-title,omitempty" json:"stagedTitle,omitempty"` // Custom styled mode title template StagedDescription string `yaml:"staged-description,omitempty" json:"stagedDescription,omitempty"` // Custom staged mode description template AppendOnlyComments bool `yaml:"append-only-comments,omitempty" json:"appendOnlyComments,omitempty"` // If true, post run status as new comments instead of updating the activation comment - ActivationComments *bool `yaml:"activation-comments,omitempty" json:"activationComments,omitempty"` // If false, disable all activation/fallback comments entirely. When nil (unset) or true, comments are enabled (default behavior) + ActivationComments string `yaml:"activation-comments,omitempty" json:"activationComments,omitempty"` // If "false", disable all activation/fallback comments entirely. Supports templatable boolean values (literal "true"/"false" or GitHub Actions expressions). Empty/unset preserves default enabled behavior. RunStarted string `yaml:"run-started,omitempty" json:"runStarted,omitempty"` // Custom workflow activation message template RunSuccess string `yaml:"run-success,omitempty" json:"runSuccess,omitempty"` // Custom workflow success message template RunFailure string `yaml:"run-failure,omitempty" json:"runFailure,omitempty"` // Custom workflow failure message template diff --git a/pkg/workflow/imports.go b/pkg/workflow/imports.go index 7693a2c0b01..0823624cf5d 100644 --- a/pkg/workflow/imports.go +++ b/pkg/workflow/imports.go @@ -693,6 +693,18 @@ func mergeMessagesConfig(result, imported *SafeOutputMessagesConfig) *SafeOutput if !result.AppendOnlyComments && imported.AppendOnlyComments { result.AppendOnlyComments = imported.AppendOnlyComments } + if result.ActivationComments == "" && imported.ActivationComments != "" { + result.ActivationComments = imported.ActivationComments + } + if result.PullRequestCreated == "" && imported.PullRequestCreated != "" { + result.PullRequestCreated = imported.PullRequestCreated + } + if result.IssueCreated == "" && imported.IssueCreated != "" { + result.IssueCreated = imported.IssueCreated + } + if result.CommitPushed == "" && imported.CommitPushed != "" { + result.CommitPushed = imported.CommitPushed + } return result } diff --git a/pkg/workflow/safe_outputs_config_messages.go b/pkg/workflow/safe_outputs_config_messages.go index 8543bb6ea7a..b3f673746d8 100644 --- a/pkg/workflow/safe_outputs_config_messages.go +++ b/pkg/workflow/safe_outputs_config_messages.go @@ -34,12 +34,10 @@ func parseMessagesConfig(messagesMap map[string]any) *SafeOutputMessagesConfig { } } - if activationComments, exists := messagesMap["activation-comments"]; exists { - if enabled, ok := activationComments.(bool); ok { - config.ActivationComments = &enabled - safeOutputMessagesLog.Printf("Set activation-comments: %t", enabled) - } + if err := preprocessBoolFieldAsString(messagesMap, "activation-comments", safeOutputMessagesLog); err != nil { + safeOutputMessagesLog.Printf("Invalid activation-comments value: %v", err) } + setStringFromMap(messagesMap, "activation-comments", &config.ActivationComments) setStringFromMap(messagesMap, "footer", &config.Footer) setStringFromMap(messagesMap, "footer-install", &config.FooterInstall) diff --git a/pkg/workflow/safe_outputs_test.go b/pkg/workflow/safe_outputs_test.go index 66966bb5e2b..69882d64196 100644 --- a/pkg/workflow/safe_outputs_test.go +++ b/pkg/workflow/safe_outputs_test.go @@ -365,6 +365,33 @@ func TestParseMessagesConfig(t *testing.T) { RunFailure: "Failure", }, }, + { + name: "activation-comments bool false is stored as string", + input: map[string]any{ + "activation-comments": false, + }, + expected: &SafeOutputMessagesConfig{ + ActivationComments: "false", + }, + }, + { + name: "activation-comments bool true is stored as string", + input: map[string]any{ + "activation-comments": true, + }, + expected: &SafeOutputMessagesConfig{ + ActivationComments: "true", + }, + }, + { + name: "activation-comments string value is stored as-is", + input: map[string]any{ + "activation-comments": "false", + }, + expected: &SafeOutputMessagesConfig{ + ActivationComments: "false", + }, + }, { name: "non-string values are ignored", input: map[string]any{ @@ -409,6 +436,9 @@ func TestParseMessagesConfig(t *testing.T) { if result.RunFailure != tt.expected.RunFailure { t.Errorf("RunFailure: got %q, want %q", result.RunFailure, tt.expected.RunFailure) } + if result.ActivationComments != tt.expected.ActivationComments { + t.Errorf("ActivationComments: got %q, want %q", result.ActivationComments, tt.expected.ActivationComments) + } }) } } From 93f6e0d3737d8422ef11d52c00f3ce5ba1239477 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 06:21:26 +0000 Subject: [PATCH 5/8] feat: use generateXMLMarker helper with id and workflow_id for activation comment footers Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_footer.cjs | 12 ++++++ actions/setup/js/generate_footer.test.cjs | 37 +++++++++++++++++++ actions/setup/js/messages_footer.cjs | 12 ++++++ .../setup/js/update_activation_comment.cjs | 27 +++++++------- .../js/update_activation_comment.test.cjs | 5 +++ 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index febb4f4ba7e..aea78d5b23a 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -43,6 +43,8 @@ function generateXMLMarker(workflowName, runUrl) { const engineVersion = process.env.GH_AW_ENGINE_VERSION || ""; const engineModel = process.env.GH_AW_ENGINE_MODEL || ""; const trackerId = process.env.GH_AW_TRACKER_ID || ""; + const runId = process.env.GITHUB_RUN_ID || ""; + const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; // Build the key-value pairs for the marker const parts = []; @@ -70,6 +72,16 @@ function generateXMLMarker(workflowName, runUrl) { parts.push(`model: ${engineModel}`); } + // Add numeric run ID if available + if (runId) { + parts.push(`id: ${runId}`); + } + + // Add workflow identifier if available + if (workflowId) { + parts.push(`workflow_id: ${workflowId}`); + } + // Always include run URL parts.push(`run: ${runUrl}`); diff --git a/actions/setup/js/generate_footer.test.cjs b/actions/setup/js/generate_footer.test.cjs index 5d4212d6a80..93d49289914 100644 --- a/actions/setup/js/generate_footer.test.cjs +++ b/actions/setup/js/generate_footer.test.cjs @@ -45,6 +45,7 @@ describe("generate_footer.cjs", () => { delete process.env.GH_AW_ENGINE_MODEL; delete process.env.GH_AW_TRACKER_ID; delete process.env.GH_AW_WORKFLOW_ID; + delete process.env.GITHUB_RUN_ID; // Dynamic import to get fresh module state const module = await import("./generate_footer.cjs"); @@ -126,6 +127,42 @@ describe("generate_footer.cjs", () => { expect(result).toBe(""); }); + + it("should include run id when GITHUB_RUN_ID env var is set", async () => { + process.env.GITHUB_RUN_ID = "9876543210"; + + vi.resetModules(); + const freshModule = await import("./generate_footer.cjs"); + + const result = freshModule.generateXMLMarker("Test Workflow", "https://github.com/test/repo/actions/runs/9876543210"); + + expect(result).toBe(""); + }); + + it("should include workflow_id when GH_AW_WORKFLOW_ID env var is set", async () => { + process.env.GH_AW_WORKFLOW_ID = "smoke-copilot"; + + vi.resetModules(); + const freshModule = await import("./generate_footer.cjs"); + + const result = freshModule.generateXMLMarker("Test Workflow", "https://github.com/test/repo/actions/runs/123"); + + expect(result).toBe(""); + }); + + it("should include all identifiers when all standard env vars are set", async () => { + process.env.GH_AW_ENGINE_ID = "copilot"; + process.env.GH_AW_TRACKER_ID = "tracker-abc"; + process.env.GITHUB_RUN_ID = "12345"; + process.env.GH_AW_WORKFLOW_ID = "my-workflow"; + + vi.resetModules(); + const freshModule = await import("./generate_footer.cjs"); + + const result = freshModule.generateXMLMarker("My Workflow", "https://github.com/test/repo/actions/runs/12345"); + + expect(result).toBe(""); + }); }); describe("generateWorkflowIdMarker", () => { diff --git a/actions/setup/js/messages_footer.cjs b/actions/setup/js/messages_footer.cjs index e72f861e116..d61f50f091d 100644 --- a/actions/setup/js/messages_footer.cjs +++ b/actions/setup/js/messages_footer.cjs @@ -171,6 +171,8 @@ function generateXMLMarker(workflowName, runUrl) { const engineVersion = process.env.GH_AW_ENGINE_VERSION || ""; const engineModel = process.env.GH_AW_ENGINE_MODEL || ""; const trackerId = process.env.GH_AW_TRACKER_ID || ""; + const runId = process.env.GITHUB_RUN_ID || ""; + const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; // Build the key-value pairs for the marker const parts = []; @@ -198,6 +200,16 @@ function generateXMLMarker(workflowName, runUrl) { parts.push(`model: ${engineModel}`); } + // Add numeric run ID if available + if (runId) { + parts.push(`id: ${runId}`); + } + + // Add workflow identifier if available + if (workflowId) { + parts.push(`workflow_id: ${workflowId}`); + } + // Always include run URL parts.push(`run: ${runUrl}`); diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index 83dc770dfdb..ba7d0cc4975 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -6,14 +6,17 @@ const { getMessages } = require("./messages_core.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getPullRequestCreatedMessage, getIssueCreatedMessage, getCommitPushedMessage } = require("./messages_run_status.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); +const { generateXMLMarker } = require("./generate_footer.cjs"); /** - * Get the current workflow run ID from context or environment. + * Build the workflow run URL from context and environment. * @param {any} context - GitHub Actions context - * @returns {string} The run ID, or empty string if not available + * @returns {string} The workflow run URL */ -function getRunId(context) { - return String(context.runId || process.env.GITHUB_RUN_ID || ""); +function getRunUrl(context) { + const runId = context.runId || process.env.GITHUB_RUN_ID || ""; + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; + return context.payload?.repository?.html_url ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; } /** @@ -27,12 +30,10 @@ function getRunId(context) { */ async function updateActivationComment(github, context, core, itemUrl, itemNumber, itemType = "pull_request") { const itemLabel = itemType === "issue" ? "issue" : "pull request"; - const runId = getRunId(context); + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "Workflow"; + const runUrl = getRunUrl(context); const body = itemType === "issue" ? getIssueCreatedMessage({ itemNumber, itemUrl }) : getPullRequestCreatedMessage({ itemNumber, itemUrl }); - let linkMessage = `\n\n${body}`; - if (runId) { - linkMessage += `\n\n`; - } + const linkMessage = `\n\n${body}\n\n${generateXMLMarker(workflowName, runUrl)}`; await updateActivationCommentWithMessage(github, context, core, linkMessage, itemLabel, { targetIssueNumber: itemNumber }); } @@ -48,11 +49,9 @@ async function updateActivationComment(github, context, core, itemUrl, itemNumbe */ async function updateActivationCommentWithCommit(github, context, core, commitSha, commitUrl, options = {}) { const shortSha = commitSha.substring(0, 7); - const runId = getRunId(context); - let message = `\n\n${getCommitPushedMessage({ commitSha, shortSha, commitUrl })}`; - if (runId) { - message += `\n\n`; - } + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "Workflow"; + const runUrl = getRunUrl(context); + const message = `\n\n${getCommitPushedMessage({ commitSha, shortSha, commitUrl })}\n\n${generateXMLMarker(workflowName, runUrl)}`; await updateActivationCommentWithMessage(github, context, core, message, "commit", options); } diff --git a/actions/setup/js/update_activation_comment.test.cjs b/actions/setup/js/update_activation_comment.test.cjs index 6370d10164a..67f2daa5a0c 100644 --- a/actions/setup/js/update_activation_comment.test.cjs +++ b/actions/setup/js/update_activation_comment.test.cjs @@ -66,6 +66,11 @@ const createTestableFunction = scriptContent => { }, }; } + if (module === "./generate_footer.cjs") { + return { + generateXMLMarker: (workflowName, runUrl) => ``, + }; + } throw new Error(`Module ${module} not mocked in test`); }; return new Function( From b49182aeee80514af02e93f80709c7b6e84f5e52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 06:57:11 +0000 Subject: [PATCH 6/8] feat: move activation-comments to safe-outputs top level Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 11 +-- pkg/workflow/safe_outputs_config.go | 13 +++ pkg/workflow/safe_outputs_config_messages.go | 5 -- pkg/workflow/safe_outputs_test.go | 86 +++++++++++++------- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 0792880c2ab..84792f8befe 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -6787,11 +6787,6 @@ "type": "boolean", "description": "When enabled, workflow completion notifier creates a new comment instead of editing the activation comment. Creates an append-only timeline of workflow runs. Default: false", "default": false - }, - "activation-comments": { - "type": ["boolean", "string"], - "description": "When set to false or \"false\", disables all activation and fallback comments entirely (run-started, run-success, run-failure, PR/issue creation links). Supports templatable boolean values including GitHub Actions expressions (e.g. ${{ inputs.activation-comments }}). Default: true", - "default": true } }, "additionalProperties": false @@ -6851,6 +6846,12 @@ "default": true, "examples": [false, true] }, + "activation-comments": { + "type": ["boolean", "string"], + "description": "When set to false or \"false\", disables all activation and fallback comments entirely (run-started, run-success, run-failure, PR/issue creation links). Supports templatable boolean values including GitHub Actions expressions (e.g. ${{ inputs.activation-comments }}). Default: true", + "default": true, + "examples": [false, true, "${{ inputs.activation-comments }}"] + }, "group-reports": { "type": "boolean", "description": "When true, creates a parent '[agentics] Failed runs' issue that tracks all workflow failures as sub-issues. Helps organize failure tracking but may be unnecessary in smaller repositories. Defaults to false.", diff --git a/pkg/workflow/safe_outputs_config.go b/pkg/workflow/safe_outputs_config.go index 3ed608382fe..d4cf92d096c 100644 --- a/pkg/workflow/safe_outputs_config.go +++ b/pkg/workflow/safe_outputs_config.go @@ -416,6 +416,19 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } + // Handle activation-comments at safe-outputs top level (templatable boolean) + if err := preprocessBoolFieldAsString(outputMap, "activation-comments", safeOutputsConfigLog); err != nil { + safeOutputsConfigLog.Printf("activation-comments: %v", err) + } + if activationComments, exists := outputMap["activation-comments"]; exists { + if activationCommentsStr, ok := activationComments.(string); ok && activationCommentsStr != "" { + if config.Messages == nil { + config.Messages = &SafeOutputMessagesConfig{} + } + config.Messages.ActivationComments = activationCommentsStr + } + } + // Handle mentions configuration if mentions, exists := outputMap["mentions"]; exists { config.Mentions = parseMentionsConfig(mentions) diff --git a/pkg/workflow/safe_outputs_config_messages.go b/pkg/workflow/safe_outputs_config_messages.go index b3f673746d8..f11818ea2b2 100644 --- a/pkg/workflow/safe_outputs_config_messages.go +++ b/pkg/workflow/safe_outputs_config_messages.go @@ -34,11 +34,6 @@ func parseMessagesConfig(messagesMap map[string]any) *SafeOutputMessagesConfig { } } - if err := preprocessBoolFieldAsString(messagesMap, "activation-comments", safeOutputMessagesLog); err != nil { - safeOutputMessagesLog.Printf("Invalid activation-comments value: %v", err) - } - setStringFromMap(messagesMap, "activation-comments", &config.ActivationComments) - setStringFromMap(messagesMap, "footer", &config.Footer) setStringFromMap(messagesMap, "footer-install", &config.FooterInstall) setStringFromMap(messagesMap, "footer-workflow-recompile", &config.FooterWorkflowRecompile) diff --git a/pkg/workflow/safe_outputs_test.go b/pkg/workflow/safe_outputs_test.go index 69882d64196..d51eb4b0c0c 100644 --- a/pkg/workflow/safe_outputs_test.go +++ b/pkg/workflow/safe_outputs_test.go @@ -365,33 +365,6 @@ func TestParseMessagesConfig(t *testing.T) { RunFailure: "Failure", }, }, - { - name: "activation-comments bool false is stored as string", - input: map[string]any{ - "activation-comments": false, - }, - expected: &SafeOutputMessagesConfig{ - ActivationComments: "false", - }, - }, - { - name: "activation-comments bool true is stored as string", - input: map[string]any{ - "activation-comments": true, - }, - expected: &SafeOutputMessagesConfig{ - ActivationComments: "true", - }, - }, - { - name: "activation-comments string value is stored as-is", - input: map[string]any{ - "activation-comments": "false", - }, - expected: &SafeOutputMessagesConfig{ - ActivationComments: "false", - }, - }, { name: "non-string values are ignored", input: map[string]any{ @@ -436,9 +409,6 @@ func TestParseMessagesConfig(t *testing.T) { if result.RunFailure != tt.expected.RunFailure { t.Errorf("RunFailure: got %q, want %q", result.RunFailure, tt.expected.RunFailure) } - if result.ActivationComments != tt.expected.ActivationComments { - t.Errorf("ActivationComments: got %q, want %q", result.ActivationComments, tt.expected.ActivationComments) - } }) } } @@ -518,6 +488,62 @@ func TestSerializeMessagesConfigComprehensive(t *testing.T) { // GenerateSafeOutputsConfig Tests // ======================================== +// TestExtractSafeOutputsConfigActivationComments tests activation-comments parsing at top-level safe-outputs +func TestExtractSafeOutputsConfigActivationComments(t *testing.T) { + tests := []struct { + name string + input map[string]any + expected string + }{ + { + name: "activation-comments bool false is stored as string in Messages", + input: map[string]any{ + "safe-outputs": map[string]any{ + "create-pull-requests": map[string]any{}, + "activation-comments": false, + }, + }, + expected: "false", + }, + { + name: "activation-comments bool true is stored as string in Messages", + input: map[string]any{ + "safe-outputs": map[string]any{ + "create-pull-requests": map[string]any{}, + "activation-comments": true, + }, + }, + expected: "true", + }, + { + name: "activation-comments string value is stored as-is in Messages", + input: map[string]any{ + "safe-outputs": map[string]any{ + "create-pull-requests": map[string]any{}, + "activation-comments": "false", + }, + }, + expected: "false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Compiler{} + result := c.extractSafeOutputsConfig(tt.input) + if result == nil { + t.Fatal("expected non-nil SafeOutputsConfig") + } + if result.Messages == nil { + t.Fatal("expected non-nil Messages in SafeOutputsConfig") + } + if result.Messages.ActivationComments != tt.expected { + t.Errorf("Messages.ActivationComments: got %q, want %q", result.Messages.ActivationComments, tt.expected) + } + }) + } +} + // TestGenerateSafeOutputsConfig tests the generateSafeOutputsConfig function func TestGenerateSafeOutputsConfig(t *testing.T) { tests := []struct { From d1536e54db50cec43fccbbe6163a6e8df889d9f0 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 23 Feb 2026 07:00:30 +0000 Subject: [PATCH 7/8] Add changeset [skip-ci] --- .changeset/patch-disable-activation-comments.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-disable-activation-comments.md diff --git a/.changeset/patch-disable-activation-comments.md b/.changeset/patch-disable-activation-comments.md new file mode 100644 index 00000000000..68cfc30e00c --- /dev/null +++ b/.changeset/patch-disable-activation-comments.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Add the `safe-outputs.activation-comments` flag so activation and fallback bot comments can be silenced and activation link messages now support templating and full XML markers. From 946ee07d0f68bd3ec846f87caa10e8c25e395e8f Mon Sep 17 00:00:00 2001 From: Smoke Test Date: Mon, 23 Feb 2026 07:04:48 +0000 Subject: [PATCH 8/8] test: Add smoke test file for run 22296016223 --- tmp-smoke-test-22296016223.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tmp-smoke-test-22296016223.txt diff --git a/tmp-smoke-test-22296016223.txt b/tmp-smoke-test-22296016223.txt new file mode 100644 index 00000000000..545d3c58579 --- /dev/null +++ b/tmp-smoke-test-22296016223.txt @@ -0,0 +1 @@ +Test file for PR push