From 852315a833879fdf4111e8cf5482983080afd59b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:11:30 +0000 Subject: [PATCH 1/8] Initial plan From 225bb61d81026e1e65a99c57dd219c6fce245958 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:34:29 +0000 Subject: [PATCH 2/8] =?UTF-8?q?Add=20history=20link=20(=E2=97=B7)=20to=20m?= =?UTF-8?q?ore=20generated=20footers:=20add=5Fcomment,=20update=5Fissue,?= =?UTF-8?q?=20update=5Fpull=5Frequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_comment.cjs | 17 ++++++++++++++++- actions/setup/js/generate_history_link.cjs | 14 +++++++++----- actions/setup/js/update_issue.cjs | 13 +++++++++++++ .../setup/js/update_pr_description_helpers.cjs | 10 ++++++---- actions/setup/js/update_pull_request.cjs | 13 +++++++++++++ 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index ebf195949e8..8f24a8bef62 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -22,6 +22,7 @@ const { logStagedPreviewInfo } = require("./staged_preview.cjs"); const { ERR_NOT_FOUND } = require("./error_codes.cjs"); const { isPayloadUserBot } = require("./resolve_mentions.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { generateHistoryUrl } = require("./generate_history_link.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "add_comment"; @@ -335,6 +336,7 @@ async function main(config = {}) { // Get workflow ID for hiding older comments const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; + const callerWorkflowId = process.env.GH_AW_CALLER_WORKFLOW_ID || ""; /** * Message handler function @@ -525,9 +527,22 @@ async function main(config = {}) { const triggeringPRNumber = context.payload.pull_request?.number; const triggeringDiscussionNumber = context.payload.discussion?.number; + // Generate history URL: use in:comments for issue/PR comments; skip for discussion comments + // (GitHub search does not support in:comments for discussions) + const historyUrl = !isDiscussion + ? generateHistoryUrl({ + owner: repoParts.owner, + repo: repoParts.repo, + itemType: "comment", + workflowCallId: callerWorkflowId, + workflowId, + serverUrl: context.serverUrl, + }) || undefined + : undefined; + if (includeFooter) { // When footer is enabled, add full footer with attribution and XML markers - processedBody += generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber).trimEnd(); + processedBody += generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber, historyUrl).trimEnd(); } else { // When footer is disabled, only add XML marker for searchability (no visible attribution text) processedBody += "\n\n" + generateXMLMarker(workflowName, runUrl); diff --git a/actions/setup/js/generate_history_link.cjs b/actions/setup/js/generate_history_link.cjs index 0e16630d25a..3bcbd1f62fb 100644 --- a/actions/setup/js/generate_history_link.cjs +++ b/actions/setup/js/generate_history_link.cjs @@ -11,14 +11,14 @@ */ /** - * @typedef {"issue" | "pull_request" | "discussion"} ItemType + * @typedef {"issue" | "pull_request" | "discussion" | "comment"} ItemType */ /** * @typedef {Object} HistoryLinkParams * @property {string} owner - Repository owner * @property {string} repo - Repository name - * @property {ItemType} itemType - Type of GitHub item: "issue", "pull_request", or "discussion" + * @property {ItemType} itemType - Type of GitHub item: "issue", "pull_request", "discussion", or "comment" * @property {string} [workflowCallId] - Caller workflow ID (e.g. "owner/repo/WorkflowName"). Takes precedence over workflowId. * @property {string} [workflowId] - Workflow identifier. Used when workflowCallId is not available. * @property {string} [serverUrl] - GitHub server URL for enterprise deployments (e.g. "https://github.example.com"). Defaults to "https://github.com". @@ -51,16 +51,17 @@ function generateHistoryUrl({ owner, repo, itemType, workflowCallId, workflowId, // Build the search query parts const queryParts = [`repo:${owner}/${repo}`]; - // Add item type qualifier (issues and PRs use is: qualifiers; discussions use type= param only) + // Add item type qualifier (issues and PRs use is: qualifiers; discussions and comments do not) if (itemType === "issue") { queryParts.push("is:issue"); } else if (itemType === "pull_request") { queryParts.push("is:pr"); } - // Search for the XML marker in the body + // Search for the XML marker in the appropriate field + // Comments use in:comments (searches comment bodies); all others use in:body queryParts.push(`"${markerId}"`); - queryParts.push("in:body"); + queryParts.push(itemType === "comment" ? "in:comments" : "in:body"); // Determine the search result type parameter let typeParam; @@ -70,6 +71,9 @@ function generateHistoryUrl({ owner, repo, itemType, workflowCallId, workflowId, typeParam = "pullrequests"; } else if (itemType === "discussion") { typeParam = "discussions"; + } else if (itemType === "comment") { + // Search issues and PRs that have matching comments + typeParam = "issues"; } const url = new URL(`${server}/search`); diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs index f79cc6e1705..dc5df34e092 100644 --- a/actions/setup/js/update_issue.cjs +++ b/actions/setup/js/update_issue.cjs @@ -17,6 +17,7 @@ const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs"); const { ERR_VALIDATION } = require("./error_codes.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { generateHistoryUrl } = require("./generate_history_link.cjs"); /** * Maximum limits for issue update parameters to prevent resource exhaustion. @@ -80,9 +81,20 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { // 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({ currentBody, @@ -92,6 +104,7 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { runUrl, workflowId, includeFooter, // Pass footer flag to helper + historyUrl, }); 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 7d1f0df30c3..65807d5e403 100644 --- a/actions/setup/js/update_pr_description_helpers.cjs +++ b/actions/setup/js/update_pr_description_helpers.cjs @@ -17,9 +17,10 @@ const { sanitizeContent } = require("./sanitize_content.cjs"); * missing info sections, blocked domains, and XML metadata marker). * @param {string} workflowName - Name of the workflow * @param {string} runUrl - URL of the workflow run + * @param {string} [historyUrl] - GitHub search URL for items created by this workflow * @returns {string} AI attribution footer */ -function buildAIFooter(workflowName, runUrl) { +function buildAIFooter(workflowName, runUrl, historyUrl) { const workflowSource = process.env.GH_AW_WORKFLOW_SOURCE ?? ""; const workflowSourceURL = process.env.GH_AW_WORKFLOW_SOURCE_URL ?? ""; // Use typeof guard since context is a global injected by the Actions Script runtime @@ -27,7 +28,7 @@ function buildAIFooter(workflowName, runUrl) { const triggeringIssueNumber = ctx?.payload?.issue?.number; const triggeringPRNumber = ctx?.payload?.pull_request?.number; const triggeringDiscussionNumber = ctx?.payload?.discussion?.number; - return generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber).trimEnd(); + return generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber, historyUrl || undefined).trimEnd(); } /** @@ -82,13 +83,14 @@ function findIsland(body, workflowId) { * @param {string} params.runUrl - URL of the workflow run * @param {string} params.workflowId - Workflow ID (stable identifier across runs) * @param {boolean} [params.includeFooter=true] - Whether to include AI-generated footer (default: true) + * @param {string} [params.historyUrl] - GitHub search URL for items created by this workflow * @returns {string} Updated body content */ function updateBody(params) { - const { currentBody, newContent, operation, workflowName, runUrl, workflowId, includeFooter = true } = params; + const { currentBody, newContent, operation, workflowName, runUrl, workflowId, includeFooter = true, historyUrl } = params; // When footer is enabled use the full footer (includes install instructions, XML marker, etc.) // When footer is disabled still add standalone workflow-id marker for searchability - const aiFooter = includeFooter ? buildAIFooter(workflowName, runUrl) : ""; + const aiFooter = includeFooter ? buildAIFooter(workflowName, runUrl, historyUrl) : ""; const workflowIdMarker = !includeFooter && workflowId ? `\n\n${generateWorkflowIdMarker(workflowId)}` : ""; // Sanitize new content to prevent injection attacks diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 729f3111967..9400f174490 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -14,6 +14,7 @@ const { createUpdateHandlerFactory, createStandardResolveNumber, createStandardF const { sanitizeTitle } = require("./sanitize_title.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { generateHistoryUrl } = require("./generate_history_link.cjs"); /** * Execute the pull request update API call @@ -47,9 +48,20 @@ async function executePRUpdate(github, context, prNumber, updateData) { // 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({ currentBody, @@ -59,6 +71,7 @@ async function executePRUpdate(github, context, prNumber, updateData) { runUrl, workflowId, includeFooter, // Pass footer flag to helper + historyUrl, }); core.info(`Will update body (length: ${apiData.body.length})`); From e7f0b406a4156e79bcd7176d722e5c6bf58e83d6 Mon Sep 17 00:00:00 2001 From: Runner Date: Tue, 3 Mar 2026 07:52:11 +0000 Subject: [PATCH 3/8] Add changeset [skip-ci] --- .changeset/patch-add-history-link-footers.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-add-history-link-footers.md diff --git a/.changeset/patch-add-history-link-footers.md b/.changeset/patch-add-history-link-footers.md new file mode 100644 index 00000000000..df3bcd39359 --- /dev/null +++ b/.changeset/patch-add-history-link-footers.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Added the clock history link (◷) to the footers generated by the add_comment, update_issue, and update_pull_request handlers so the new helpers match existing issue/PR workflows. From b82769aaaf12dc85021e02aa03309e9af0001ee8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 3 Mar 2026 07:53:29 +0000 Subject: [PATCH 4/8] ci: trigger checks From ccad1df6d63c91e8a1c8779a3c4af52a65307e78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:04:09 +0000 Subject: [PATCH 5/8] Remove type filter for comment history search - search all item types Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_history_link.cjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actions/setup/js/generate_history_link.cjs b/actions/setup/js/generate_history_link.cjs index 3bcbd1f62fb..a8c66342153 100644 --- a/actions/setup/js/generate_history_link.cjs +++ b/actions/setup/js/generate_history_link.cjs @@ -71,10 +71,8 @@ function generateHistoryUrl({ owner, repo, itemType, workflowCallId, workflowId, typeParam = "pullrequests"; } else if (itemType === "discussion") { typeParam = "discussions"; - } else if (itemType === "comment") { - // Search issues and PRs that have matching comments - typeParam = "issues"; } + // For "comment" itemType, no type filter is applied so all item types are searched const url = new URL(`${server}/search`); url.searchParams.set("q", queryParts.join(" ")); From d01c140249b87a383910f0655f606dc78db0088d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:15:29 +0000 Subject: [PATCH 6/8] Remove all type filters from history search URLs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_history_link.cjs | 14 ------------- .../setup/js/generate_history_link.test.cjs | 20 +++++++++---------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/actions/setup/js/generate_history_link.cjs b/actions/setup/js/generate_history_link.cjs index a8c66342153..a42a3e729da 100644 --- a/actions/setup/js/generate_history_link.cjs +++ b/actions/setup/js/generate_history_link.cjs @@ -63,22 +63,8 @@ function generateHistoryUrl({ owner, repo, itemType, workflowCallId, workflowId, queryParts.push(`"${markerId}"`); queryParts.push(itemType === "comment" ? "in:comments" : "in:body"); - // Determine the search result type parameter - let typeParam; - if (itemType === "issue") { - typeParam = "issues"; - } else if (itemType === "pull_request") { - typeParam = "pullrequests"; - } else if (itemType === "discussion") { - typeParam = "discussions"; - } - // For "comment" itemType, no type filter is applied so all item types are searched - const url = new URL(`${server}/search`); url.searchParams.set("q", queryParts.join(" ")); - if (typeParam) { - url.searchParams.set("type", typeParam); - } return url.toString(); } diff --git a/actions/setup/js/generate_history_link.test.cjs b/actions/setup/js/generate_history_link.test.cjs index 9b1a5d73044..0614df0575c 100644 --- a/actions/setup/js/generate_history_link.test.cjs +++ b/actions/setup/js/generate_history_link.test.cjs @@ -45,7 +45,7 @@ describe("generate_history_link.cjs", () => { }); expect(url).toContain("is%3Aissue"); - expect(url).toContain("type=issues"); + expect(url).not.toContain("type="); }); it("should include is:pr qualifier for pull_request type", () => { @@ -58,7 +58,7 @@ describe("generate_history_link.cjs", () => { }); expect(url).toContain("is%3Apr"); - expect(url).toContain("type=pullrequests"); + expect(url).not.toContain("type="); }); it("should NOT include is: qualifier for discussion type", () => { @@ -71,7 +71,7 @@ describe("generate_history_link.cjs", () => { }); expect(url).not.toContain("is%3A"); - expect(url).toContain("type=discussions"); + expect(url).not.toContain("type="); }); }); @@ -295,7 +295,7 @@ describe("generate_history_link.cjs", () => { serverUrl: "https://github.com", }); - expect(url).toContain("type="); + expect(url).not.toContain("type="); }); it("should generate a complete issue search URL", () => { @@ -310,7 +310,7 @@ describe("generate_history_link.cjs", () => { const parsed = new URL(url); expect(parsed.hostname).toBe("github.com"); expect(parsed.pathname).toBe("/search"); - expect(parsed.searchParams.get("type")).toBe("issues"); + expect(parsed.searchParams.get("type")).toBeNull(); const query = parsed.searchParams.get("q"); expect(query).toContain("repo:myowner/myrepo"); @@ -329,7 +329,7 @@ describe("generate_history_link.cjs", () => { }); const parsed = new URL(url); - expect(parsed.searchParams.get("type")).toBe("pullrequests"); + expect(parsed.searchParams.get("type")).toBeNull(); const query = parsed.searchParams.get("q"); expect(query).toContain("is:pr"); @@ -346,7 +346,7 @@ describe("generate_history_link.cjs", () => { }); const parsed = new URL(url); - expect(parsed.searchParams.get("type")).toBe("discussions"); + expect(parsed.searchParams.get("type")).toBeNull(); const query = parsed.searchParams.get("q"); expect(query).not.toContain("is:issue"); @@ -430,7 +430,7 @@ describe("generate_history_link.cjs", () => { serverUrl: "https://github.com", }); - expect(link).toContain("type=issues"); + expect(link).not.toContain("type="); expect(link).toContain("is%3Aissue"); }); @@ -443,7 +443,7 @@ describe("generate_history_link.cjs", () => { serverUrl: "https://github.com", }); - expect(link).toContain("type=pullrequests"); + expect(link).not.toContain("type="); }); it("should generate link with correct search URL for discussion", () => { @@ -455,7 +455,7 @@ describe("generate_history_link.cjs", () => { serverUrl: "https://github.com", }); - expect(link).toContain("type=discussions"); + expect(link).not.toContain("type="); }); it("should support enterprise URLs in the history link", () => { From 405dd9f9d20ed38b95d95cbc5ed04720794a5842 Mon Sep 17 00:00:00 2001 From: Runner Date: Tue, 3 Mar 2026 08:52:39 +0000 Subject: [PATCH 7/8] Add changeset [skip-ci] --- .changeset/patch-add-history-link-add-comment-issue-pr.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-add-history-link-add-comment-issue-pr.md diff --git a/.changeset/patch-add-history-link-add-comment-issue-pr.md b/.changeset/patch-add-history-link-add-comment-issue-pr.md new file mode 100644 index 00000000000..5cea3b558ad --- /dev/null +++ b/.changeset/patch-add-history-link-add-comment-issue-pr.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Added the clock history link (◷) to the add_comment, update_issue, and update_pull_request footers so generated updates match the other handlers that already include the history link. From 3d2ebde3cc37a2472c125f4ad66defee0efd7fb5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 3 Mar 2026 08:54:30 +0000 Subject: [PATCH 8/8] ci: trigger checks