From 3ab84ec4bb4aaa518fa6a498850327c1d7e67132 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 01:59:50 +0000 Subject: [PATCH 1/4] Initial plan From 67e2f172ac34ddcb1469c886e5c01d2101a10684 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:12:46 +0000 Subject: [PATCH 2/4] Add target field support to add-labels safe output Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/add_labels.js | 47 +++++-- pkg/workflow/js/add_labels.test.cjs | 203 +++++++++++++++++++++++++++- pkg/workflow/js/add_labels.ts | 57 ++++++-- 3 files changed, 280 insertions(+), 27 deletions(-) diff --git a/pkg/workflow/js/add_labels.js b/pkg/workflow/js/add_labels.js index 78cbb65870e..3bb1f76a682 100644 --- a/pkg/workflow/js/add_labels.js +++ b/pkg/workflow/js/add_labels.js @@ -74,33 +74,56 @@ async function main() { return; } core.debug(`Max count: ${maxCount}`); + const labelsTarget = process.env.GITHUB_AW_LABELS_TARGET || "triggering"; + core.info(`Labels target configuration: ${labelsTarget}`); const isIssueContext = context.eventName === "issues" || context.eventName === "issue_comment"; const isPRContext = context.eventName === "pull_request" || context.eventName === "pull_request_review" || context.eventName === "pull_request_review_comment"; - if (!isIssueContext && !isPRContext) { - core.setFailed("Not running in issue or pull request context, skipping label addition"); + if (labelsTarget === "triggering" && !isIssueContext && !isPRContext) { + core.info('Target is "triggering" but not running in issue or pull request context, skipping label addition'); return; } let issueNumber; let contextType; - if (isIssueContext) { - if (context.payload.issue) { - issueNumber = context.payload.issue.number; + if (labelsTarget === "*") { + if (labelsItem.issue_number) { + issueNumber = typeof labelsItem.issue_number === "number" ? labelsItem.issue_number : parseInt(String(labelsItem.issue_number), 10); + if (isNaN(issueNumber) || issueNumber <= 0) { + core.setFailed(`Invalid issue number specified: ${labelsItem.issue_number}`); + return; + } contextType = "issue"; } else { - core.setFailed("Issue context detected but no issue found in payload"); + core.setFailed('Target is "*" but no issue_number specified in labels item'); return; } - } else if (isPRContext) { - if (context.payload.pull_request) { - issueNumber = context.payload.pull_request.number; - contextType = "pull request"; - } else { - core.setFailed("Pull request context detected but no pull request found in payload"); + } else if (labelsTarget && labelsTarget !== "triggering") { + issueNumber = parseInt(labelsTarget, 10); + if (isNaN(issueNumber) || issueNumber <= 0) { + core.setFailed(`Invalid issue number in target configuration: ${labelsTarget}`); return; } + contextType = "issue"; + } else { + if (isIssueContext) { + if (context.payload.issue) { + issueNumber = context.payload.issue.number; + contextType = "issue"; + } else { + core.setFailed("Issue context detected but no issue found in payload"); + return; + } + } else if (isPRContext) { + if (context.payload.pull_request) { + issueNumber = context.payload.pull_request.number; + contextType = "pull request"; + } else { + core.setFailed("Pull request context detected but no pull request found in payload"); + return; + } + } } if (!issueNumber) { core.setFailed("Could not determine issue or pull request number"); diff --git a/pkg/workflow/js/add_labels.test.cjs b/pkg/workflow/js/add_labels.test.cjs index 45d2b511edd..2c909ddd466 100644 --- a/pkg/workflow/js/add_labels.test.cjs +++ b/pkg/workflow/js/add_labels.test.cjs @@ -255,7 +255,7 @@ describe("add_labels.js", () => { }); describe("Context validation", () => { - it("should fail when not in issue or PR context", async () => { + it("should skip when not in issue or PR context (with default target)", async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [ { @@ -270,7 +270,7 @@ describe("add_labels.js", () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(mockCore.setFailed).toHaveBeenCalledWith("Not running in issue or pull request context, skipping label addition"); + expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue or pull request context, skipping label addition'); expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); }); @@ -857,4 +857,203 @@ describe("add_labels.js", () => { }); }); }); + + describe("Target configuration", () => { + beforeEach(() => { + // Reset environment variables + delete process.env.GITHUB_AW_LABELS_TARGET; + }); + + it("should use triggering issue when target is not specified (default behavior)", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug"], + }, + ], + }); + + global.context.eventName = "issues"; + global.context.payload.issue = { number: 456 }; + + mockGithub.rest.issues.addLabels.mockResolvedValue({}); + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith("Labels target configuration: triggering"); + expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + issue_number: 456, + labels: ["bug"], + }); + }); + + it("should use triggering issue when target is explicitly set to 'triggering'", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["enhancement"], + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "triggering"; + + global.context.eventName = "issues"; + global.context.payload.issue = { number: 789 }; + + mockGithub.rest.issues.addLabels.mockResolvedValue({}); + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith("Labels target configuration: triggering"); + expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + issue_number: 789, + labels: ["enhancement"], + }); + }); + + it("should skip when target is 'triggering' but not in issue context", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug"], + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "triggering"; + + // Set context to something other than issues or PR + global.context.eventName = "push"; + delete global.context.payload.issue; + delete global.context.payload.pull_request; + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue or pull request context, skipping label addition'); + expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); + }); + + it("should use explicit issue number from target configuration", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug", "urgent"], + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "999"; + + // Context doesn't matter when explicit issue number is provided + global.context.eventName = "push"; + delete global.context.payload.issue; + + mockGithub.rest.issues.addLabels.mockResolvedValue({}); + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith("Labels target configuration: 999"); + expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + issue_number: 999, + labels: ["bug", "urgent"], + }); + }); + + it("should use issue_number from labels item when target is '*'", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["documentation"], + issue_number: 555, + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "*"; + + // Context doesn't matter when issue_number is provided in the item + global.context.eventName = "push"; + delete global.context.payload.issue; + + mockGithub.rest.issues.addLabels.mockResolvedValue({}); + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith("Labels target configuration: *"); + expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + issue_number: 555, + labels: ["documentation"], + }); + }); + + it("should fail when target is '*' but no issue_number in labels item", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug"], + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "*"; + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith('Target is "*" but no issue_number specified in labels item'); + expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); + }); + + it("should fail when target has invalid issue number", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug"], + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "invalid"; + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith("Invalid issue number in target configuration: invalid"); + expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); + }); + + it("should fail when target is '*' and issue_number in item is invalid", async () => { + process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "add-labels", + labels: ["bug"], + issue_number: -5, + }, + ], + }); + process.env.GITHUB_AW_LABELS_TARGET = "*"; + + // Execute the script + await eval(`(async () => { ${addLabelsScript} })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith("Invalid issue number specified: -5"); + expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); + }); + }); }); diff --git a/pkg/workflow/js/add_labels.ts b/pkg/workflow/js/add_labels.ts index ea47a2c9be4..079b7cbd3e4 100644 --- a/pkg/workflow/js/add_labels.ts +++ b/pkg/workflow/js/add_labels.ts @@ -114,6 +114,10 @@ async function main() { core.debug(`Max count: ${maxCount}`); + // Get the target configuration from environment variable + const labelsTarget = process.env.GITHUB_AW_LABELS_TARGET || "triggering"; + core.info(`Labels target configuration: ${labelsTarget}`); + // Check if we're in an issue or pull request context const isIssueContext = context.eventName === "issues" || context.eventName === "issue_comment"; const isPRContext = @@ -121,31 +125,58 @@ async function main() { context.eventName === "pull_request_review" || context.eventName === "pull_request_review_comment"; - if (!isIssueContext && !isPRContext) { - core.setFailed("Not running in issue or pull request context, skipping label addition"); + // Validate context based on target configuration + if (labelsTarget === "triggering" && !isIssueContext && !isPRContext) { + core.info('Target is "triggering" but not running in issue or pull request context, skipping label addition'); return; } - // Determine the issue/PR number + // Determine the issue/PR number based on target configuration let issueNumber; let contextType; - if (isIssueContext) { - if (context.payload.issue) { - issueNumber = context.payload.issue.number; + if (labelsTarget === "*") { + // For target "*", we need an explicit issue number from the labels item + if (labelsItem.issue_number) { + issueNumber = typeof labelsItem.issue_number === "number" + ? labelsItem.issue_number + : parseInt(String(labelsItem.issue_number), 10); + if (isNaN(issueNumber) || issueNumber <= 0) { + core.setFailed(`Invalid issue number specified: ${labelsItem.issue_number}`); + return; + } contextType = "issue"; } else { - core.setFailed("Issue context detected but no issue found in payload"); + core.setFailed('Target is "*" but no issue_number specified in labels item'); return; } - } else if (isPRContext) { - if (context.payload.pull_request) { - issueNumber = context.payload.pull_request.number; - contextType = "pull request"; - } else { - core.setFailed("Pull request context detected but no pull request found in payload"); + } else if (labelsTarget && labelsTarget !== "triggering") { + // Explicit issue number specified in target + issueNumber = parseInt(labelsTarget, 10); + if (isNaN(issueNumber) || issueNumber <= 0) { + core.setFailed(`Invalid issue number in target configuration: ${labelsTarget}`); return; } + contextType = "issue"; + } else { + // Default behavior: use triggering issue/PR + if (isIssueContext) { + if (context.payload.issue) { + issueNumber = context.payload.issue.number; + contextType = "issue"; + } else { + core.setFailed("Issue context detected but no issue found in payload"); + return; + } + } else if (isPRContext) { + if (context.payload.pull_request) { + issueNumber = context.payload.pull_request.number; + contextType = "pull request"; + } else { + core.setFailed("Pull request context detected but no pull request found in payload"); + return; + } + } } if (!issueNumber) { From 59876c7fcd78a3a1a7e587f8c72293a305ee907c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:20:53 +0000 Subject: [PATCH 3/4] Update documentation for add-labels target field Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- docs/src/content/docs/reference/safe-outputs.md | 12 ++++++++---- pkg/workflow/js/add_labels.test.cjs | 8 ++++++-- pkg/workflow/js/add_labels.ts | 4 +--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 0965d2e80b0..d8114d4a510 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -118,20 +118,24 @@ The compiled workflow will have additional prompting describing that, to create ### Add Issue Label (`add-labels:`) -Adding `add-labels:` to the `safe-outputs:` section of your workflow declares that the workflow should conclude with adding labels to the current issue or pull request based on the coding agent's analysis. +Adding `add-labels:` to the `safe-outputs:` section of your workflow declares that the workflow should conclude with adding labels to issues or pull requests based on the coding agent's analysis. By default, labels are added to the triggering issue or pull request, but this can be configured using the `target` option. +**Basic Configuration:** ```yaml safe-outputs: add-labels: ``` -or with further configuration: - +**With Configuration:** ```yaml safe-outputs: add-labels: - allowed: [triage, bug, enhancement] # Optional: allowed labels for addition. + allowed: [triage, bug, enhancement] # Optional: allowed labels for addition max: 3 # Optional: maximum number of labels to add (default: 3) + target: "*" # Optional: target for labels + # "triggering" (default) - only add labels to triggering issue/PR + # "*" - allow labels on any issue (requires issue_number in agent output) + # Explicit number - add labels to specific issue/PR (e.g., "123") ``` The agentic part of your workflow should analyze the issue content and determine appropriate labels. diff --git a/pkg/workflow/js/add_labels.test.cjs b/pkg/workflow/js/add_labels.test.cjs index 2c909ddd466..864bcdca171 100644 --- a/pkg/workflow/js/add_labels.test.cjs +++ b/pkg/workflow/js/add_labels.test.cjs @@ -270,7 +270,9 @@ describe("add_labels.js", () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue or pull request context, skipping label addition'); + expect(mockCore.info).toHaveBeenCalledWith( + 'Target is "triggering" but not running in issue or pull request context, skipping label addition' + ); expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); }); @@ -938,7 +940,9 @@ describe("add_labels.js", () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue or pull request context, skipping label addition'); + expect(mockCore.info).toHaveBeenCalledWith( + 'Target is "triggering" but not running in issue or pull request context, skipping label addition' + ); expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); }); diff --git a/pkg/workflow/js/add_labels.ts b/pkg/workflow/js/add_labels.ts index 079b7cbd3e4..5188f47d52d 100644 --- a/pkg/workflow/js/add_labels.ts +++ b/pkg/workflow/js/add_labels.ts @@ -138,9 +138,7 @@ async function main() { if (labelsTarget === "*") { // For target "*", we need an explicit issue number from the labels item if (labelsItem.issue_number) { - issueNumber = typeof labelsItem.issue_number === "number" - ? labelsItem.issue_number - : parseInt(String(labelsItem.issue_number), 10); + issueNumber = typeof labelsItem.issue_number === "number" ? labelsItem.issue_number : parseInt(String(labelsItem.issue_number), 10); if (isNaN(issueNumber) || issueNumber <= 0) { core.setFailed(`Invalid issue number specified: ${labelsItem.issue_number}`); return; From 24d60f03b2536bf9965ccf6f19683a34a766bcc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:30:12 +0000 Subject: [PATCH 4/4] Add test workflow for add-labels target feature Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/workflows/test-add-labels-target.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 pkg/cli/workflows/test-add-labels-target.md diff --git a/pkg/cli/workflows/test-add-labels-target.md b/pkg/cli/workflows/test-add-labels-target.md new file mode 100644 index 00000000000..485076ebf43 --- /dev/null +++ b/pkg/cli/workflows/test-add-labels-target.md @@ -0,0 +1,21 @@ +--- +on: + workflow_dispatch: +permissions: + contents: read + actions: read +engine: claude +safe-outputs: + add-labels: + allowed: [bug, enhancement, documentation] + target: "*" +--- + +# Test Add Labels with Target + +This workflow demonstrates the `target` field for `add-labels`. + +With `target: "*"`, the workflow can add labels to any issue by specifying +the `issue_number` in the output. + +Please add the label "bug" to issue #1 and the label "documentation" to issue #2.