From a22eb89370d811b94fad757f344212fd065cfb28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:05:00 +0000 Subject: [PATCH 1/2] Initial plan From caab21661addee007a4344e54a48f110f4ba3a40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:11:50 +0000 Subject: [PATCH 2/2] Deduplicate safe-output body attribution flow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_issue.cjs | 35 +++------------- .../js/update_pr_description_helpers.cjs | 42 +++++++++++++++++++ .../js/update_pr_description_helpers.test.cjs | 25 ++++++++++- actions/setup/js/update_pull_request.cjs | 35 +++------------- 4 files changed, 78 insertions(+), 59 deletions(-) diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs index cd91aec6506..55f7bc3658c 100644 --- a/actions/setup/js/update_issue.cjs +++ b/actions/setup/js/update_issue.cjs @@ -10,13 +10,11 @@ const HANDLER_TYPE = "update_issue"; const { resolveTarget, checkRequiredFilter } = require("./safe_output_helpers.cjs"); const { createUpdateHandlerFactory, createStandardResolveNumber, createStandardFormatResult } = require("./update_handler_factory.cjs"); -const { updateBody } = require("./update_pr_description_helpers.cjs"); +const { buildUpdatedBody } = require("./update_pr_description_helpers.cjs"); const { buildCommonEntityUpdateData } = require("./update_entity_helpers.cjs"); const { loadTemporaryProjectMap, replaceTemporaryProjectReferences } = require("./temporary_id.cjs"); const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs"); const { ERR_VALIDATION } = require("./error_codes.cjs"); -const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); -const { generateHistoryUrl } = require("./generate_history_link.cjs"); const { fetchIssueState, mergeIssueState } = require("./safe_output_execution_metadata.cjs"); const { MAX_LABELS, MAX_ASSIGNEES } = require("./constants.cjs"); const { fetchAllRepoLabels } = require("./github_api_helpers.cjs"); @@ -82,35 +80,14 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { const currentBody = currentIssue.body || ""; - // Get workflow run URL for AI attribution. - // Use the original workflow repo (_workflowRepo) rather than context.repo, because - // context may be effectiveContext with repo overridden to a cross-repo target. - const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; - const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; - const callerWorkflowId = process.env.GH_AW_CALLER_WORKFLOW_ID || ""; - const workflowRepo = _workflowRepo || context.repo; - const runUrl = buildWorkflowRunUrl(context, workflowRepo); - - const historyUrl = - generateHistoryUrl({ - owner: context.repo.owner, - repo: context.repo.repo, - itemType: "issue", - workflowCallId: callerWorkflowId, - workflowId, - serverUrl: context.serverUrl, - }) || undefined; - - // Use helper to update body (handles all operations including replace) - apiData.body = updateBody({ + apiData.body = buildUpdatedBody({ + context, currentBody, newContent: rawBody, operation, - workflowName, - runUrl, - workflowId, - includeFooter, // Pass footer flag to helper - historyUrl, + includeFooter, + workflowRepo: _workflowRepo, + itemType: "issue", }); core.info(`Will update body (length: ${apiData.body.length})`); diff --git a/actions/setup/js/update_pr_description_helpers.cjs b/actions/setup/js/update_pr_description_helpers.cjs index 09791c6f08c..13ac2d6e4a2 100644 --- a/actions/setup/js/update_pr_description_helpers.cjs +++ b/actions/setup/js/update_pr_description_helpers.cjs @@ -10,6 +10,8 @@ const { assembleMarkdownBodyParts, buildGeneratedFooter } = require("./markdown_body_helpers.cjs"); const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); +const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { generateHistoryUrl } = require("./generate_history_link.cjs"); /** * Build the AI footer with workflow attribution @@ -149,8 +151,48 @@ function updateBody(params) { return currentBody + appendSection; } +/** + * Build an updated entity body with workflow attribution. + * @param {Object} params - Body update parameters + * @param {any} params.context - GitHub Actions context for the target repository + * @param {string} params.currentBody - Current body content + * @param {string} params.newContent - New content to add or replace + * @param {string} params.operation - Body update operation + * @param {boolean} params.includeFooter - Whether to include the generated footer + * @param {any} [params.workflowRepo] - Original workflow repository for run attribution + * @param {"issue" | "pull_request"} params.itemType - Updated entity type + * @returns {string} Updated body content + */ +function buildUpdatedBody({ context, currentBody, newContent, operation, includeFooter, workflowRepo, itemType }) { + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; + const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; + const workflowCallId = process.env.GH_AW_CALLER_WORKFLOW_ID || ""; + const runUrl = buildWorkflowRunUrl(context, workflowRepo || context.repo); + const historyUrl = + generateHistoryUrl({ + owner: context.repo.owner, + repo: context.repo.repo, + itemType, + workflowCallId, + workflowId, + serverUrl: context.serverUrl, + }) || undefined; + + return updateBody({ + currentBody, + newContent, + operation, + workflowName, + runUrl, + workflowId, + includeFooter, + historyUrl, + }); +} + module.exports = { buildAIFooter, + buildUpdatedBody, buildIslandStartMarker, buildIslandEndMarker, findIsland, diff --git a/actions/setup/js/update_pr_description_helpers.test.cjs b/actions/setup/js/update_pr_description_helpers.test.cjs index ff54c39136e..20d5c37866a 100644 --- a/actions/setup/js/update_pr_description_helpers.test.cjs +++ b/actions/setup/js/update_pr_description_helpers.test.cjs @@ -11,7 +11,7 @@ const mockCore = { global.core = mockCore; // Import the module -const { buildAIFooter, buildIslandStartMarker, buildIslandEndMarker, findIsland, updateBody } = await import("./update_pr_description_helpers.cjs"); +const { buildAIFooter, buildUpdatedBody, buildIslandStartMarker, buildIslandEndMarker, findIsland, updateBody } = await import("./update_pr_description_helpers.cjs"); describe("update_pr_description_helpers.cjs", () => { beforeEach(() => { @@ -45,6 +45,29 @@ describe("update_pr_description_helpers.cjs", () => { }); }); + describe("buildUpdatedBody", () => { + it("uses the workflow repository for attribution and target repository for history", () => { + process.env.GH_AW_CALLER_WORKFLOW_ID = "caller-workflow"; + const result = buildUpdatedBody({ + context: { + repo: { owner: "target", repo: "repository" }, + serverUrl: "https://github.example", + runId: 123, + }, + currentBody: "Existing body", + newContent: "New body", + operation: "append", + includeFooter: true, + workflowRepo: { owner: "workflow", repo: "repository" }, + itemType: "issue", + }); + + expect(result).toContain("https://github.example/workflow/repository/actions/runs/123"); + expect(result).toContain("repo%3Atarget%2Frepository"); + delete process.env.GH_AW_CALLER_WORKFLOW_ID; + }); + }); + describe("buildIslandStartMarker", () => { it("should build island start marker with workflow ID", () => { const marker = buildIslandStartMarker("test-workflow"); diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 0dfd6044e90..7977e1e8e07 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -9,12 +9,10 @@ /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "update_pull_request"; -const { updateBody } = require("./update_pr_description_helpers.cjs"); +const { buildUpdatedBody } = require("./update_pr_description_helpers.cjs"); const { resolveTarget, checkRequiredFilter } = require("./safe_output_helpers.cjs"); const { createUpdateHandlerFactory, createStandardResolveNumber, createStandardFormatResult } = require("./update_handler_factory.cjs"); const { buildCommonEntityUpdateData } = require("./update_entity_helpers.cjs"); -const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); -const { generateHistoryUrl } = require("./generate_history_link.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { fetchPullRequestState, mergePullRequestState } = require("./safe_output_execution_metadata.cjs"); const { withRetry, isTransientError } = require("./error_recovery.cjs"); @@ -132,35 +130,14 @@ async function executePRUpdate(github, context, prNumber, updateData) { }); const currentBody = currentPR.body || ""; - // Get workflow run URL for AI attribution. - // Use the original workflow repo (_workflowRepo) rather than context.repo, because - // context may be effectiveContext with repo overridden to a cross-repo target. - const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; - const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; - const callerWorkflowId = process.env.GH_AW_CALLER_WORKFLOW_ID || ""; - const workflowRepo = _workflowRepo || context.repo; - const runUrl = buildWorkflowRunUrl(context, workflowRepo); - - const historyUrl = - generateHistoryUrl({ - owner: context.repo.owner, - repo: context.repo.repo, - itemType: "pull_request", - workflowCallId: callerWorkflowId, - workflowId, - serverUrl: context.serverUrl, - }) || undefined; - - // Use helper to update body (handles all operations including replace) - apiData.body = updateBody({ + apiData.body = buildUpdatedBody({ + context, currentBody, newContent: rawBody, operation, - workflowName, - runUrl, - workflowId, - includeFooter, // Pass footer flag to helper - historyUrl, + includeFooter, + workflowRepo: _workflowRepo, + itemType: "pull_request", }); core.info(`Will update body (length: ${apiData.body.length})`);