From eab5dc8d4aea9bf80e2ca99ee9dfef3e376c0885 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:48:53 +0000 Subject: [PATCH 1/4] Initial plan From 5b916b3b2d0e7f0f2e17e6b00fe0723af8e7aa6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:54:12 +0000 Subject: [PATCH 2/4] initial plan Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b4681576-a2d6-406e-982c-95c92bc37bd7 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 5dac0c9a07a..8c142f3ab1a 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -281,7 +281,7 @@ jobs: validate_workflows: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'validate' && !github.event.repository.fork }} - runs-on: ubuntu-latest + runs-on: ubuntu-slim permissions: contents: read issues: write @@ -315,11 +315,6 @@ jobs: - name: Build gh-aw run: make build - - name: Start Docker daemon - run: | - sudo systemctl start docker - docker info - - name: Validate workflows and file issue on findings uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: From 4fe225766c21668530d44c54070b51fc683da2c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:59:02 +0000 Subject: [PATCH 3/4] fix: skip duplicate detection when a different pull_request_repo is specified When assign_to_agent is called for an issue where the agent is already assigned, allow re-assignment if a pull_request_repo is specified. This enables multi-platform workflows where the same issue triggers Copilot sessions in different target repositories. Fixes github/gh-aw#6587 Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b4681576-a2d6-406e-982c-95c92bc37bd7 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/assign_to_agent.cjs | 6 +- actions/setup/js/assign_to_agent.test.cjs | 70 +++++++++++++++++++++++ pkg/workflow/maintenance_workflow.go | 4 +- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index ef4997239ed..4dde70e438d 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -351,8 +351,10 @@ async function main(config = {}) { core.info(`${type} ID: ${assignableId}`); - // Skip if agent is already assigned - if (currentAssignees.some(a => a.id === agentId)) { + // Skip if agent is already assigned and no explicit pull_request_repo is specified. + // When a different pull_request_repo is provided, allow re-assignment so Copilot + // can be triggered for a different target repository on the same issue. + if (currentAssignees.some(a => a.id === agentId) && !effectivePullRequestRepoId) { core.info(`${agentName} is already assigned to ${type} #${number}`); _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, success: true }); return { success: true }; diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs index 33fc6f7c3d2..28e7ff2acd2 100644 --- a/actions/setup/js/assign_to_agent.test.cjs +++ b/actions/setup/js/assign_to_agent.test.cjs @@ -388,6 +388,76 @@ describe("assign_to_agent", () => { expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #42")); }); + it("should allow re-assignment when agent is already assigned but pull_request_repo differs", async () => { + process.env.GH_AW_AGENT_PULL_REQUEST_REPO = "test-owner/default-pr-repo"; + process.env.GH_AW_AGENT_ALLOWED_PULL_REQUEST_REPOS = "test-owner/other-platform-repo"; + setAgentOutput({ + items: [ + { + type: "assign_to_agent", + issue_number: 42, + agent: "copilot", + pull_request_repo: "test-owner/other-platform-repo", + }, + ], + errors: [], + }); + + // Mock GraphQL responses + mockGithub.graphql + // Get global PR repository ID and default branch + .mockResolvedValueOnce({ + repository: { + id: "default-pr-repo-id", + defaultBranchRef: { name: "main" }, + }, + }) + // Get per-item PR repository ID + .mockResolvedValueOnce({ + repository: { + id: "other-platform-repo-id", + }, + }) + // Find agent + .mockResolvedValueOnce({ + repository: { + suggestedActors: { + nodes: [{ login: "copilot-swe-agent", id: "agent-id" }], + }, + }, + }) + // Get issue details - agent is already assigned + .mockResolvedValueOnce({ + repository: { + issue: { + id: "issue-id", + assignees: { + nodes: [{ id: "agent-id", login: "copilot-swe-agent" }], + }, + }, + }, + }) + // Assign agent (should proceed despite already being assigned) + .mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`); + + // Should NOT see "already assigned" skip message + expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringContaining("is already assigned to issue #42")); + // Should see successful assignment + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Successfully assigned copilot coding agent to issue #42")); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + + // Verify the mutation was called with the per-item PR repo ID + const lastGraphQLCall = mockGithub.graphql.mock.calls[mockGithub.graphql.mock.calls.length - 1]; + expect(lastGraphQLCall[0]).toContain("agentAssignment"); + expect(lastGraphQLCall[1].targetRepoId).toBe("other-platform-repo-id"); + }); + it("should handle API errors gracefully", async () => { setAgentOutput({ items: [ diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index c7c2b1fb2ee..5f582cd2335 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -737,7 +737,7 @@ func buildNotDispatchOrEmptyOperation() ConditionNode { // buildNotForkAndScheduledOrOperation creates a condition for jobs that run on // schedule (or empty operation) AND when a specific operation is selected. -// Condition: !fork && (not_dispatch || operation == '' || operation == op) +// Condition: !fork && (not_dispatch || operation == ” || operation == op) func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { return BuildAnd( buildNotForkCondition(), @@ -753,7 +753,7 @@ func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { // buildRunOperationCondition creates the condition for the unified run_operation // job that handles all dispatch operations except the ones with dedicated jobs. -// Condition: dispatch && operation != '' && operation != each excluded && !fork. +// Condition: dispatch && operation != ” && operation != each excluded && !fork. func buildRunOperationCondition(excludedOperations ...string) ConditionNode { // Start with: event is workflow_dispatch AND operation is not empty condition := BuildAnd( From 001ba4e2624aecbb35262ad3bc89f2638aadf490 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 16:31:49 +0000 Subject: [PATCH 4/4] fix: use per-item pull_request_repo for dedup guard, revert maintenance changes Address PR review feedback: - Use message.pull_request_repo (per-item override) instead of effectivePullRequestRepoId (which includes global config) for the already-assigned dedup guard - Add test for global pull-request-repo with already-assigned agent (no per-item override) to verify dedup still works - Revert unrelated agentics-maintenance.yml and maintenance_workflow.go changes Agent-Logs-Url: https://github.com/github/gh-aw/sessions/9202ff8f-8030-4a1e-835d-f6ed3e77b236 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 7 ++- actions/setup/js/assign_to_agent.cjs | 10 +++-- actions/setup/js/assign_to_agent.test.cjs | 51 ++++++++++++++++++++++ pkg/workflow/maintenance_workflow.go | 4 +- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 8c142f3ab1a..5dac0c9a07a 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -281,7 +281,7 @@ jobs: validate_workflows: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'validate' && !github.event.repository.fork }} - runs-on: ubuntu-slim + runs-on: ubuntu-latest permissions: contents: read issues: write @@ -315,6 +315,11 @@ jobs: - name: Build gh-aw run: make build + - name: Start Docker daemon + run: | + sudo systemctl start docker + docker info + - name: Validate workflows and file issue on findings uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index 4dde70e438d..8a308cea146 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -351,10 +351,12 @@ async function main(config = {}) { core.info(`${type} ID: ${assignableId}`); - // Skip if agent is already assigned and no explicit pull_request_repo is specified. - // When a different pull_request_repo is provided, allow re-assignment so Copilot - // can be triggered for a different target repository on the same issue. - if (currentAssignees.some(a => a.id === agentId) && !effectivePullRequestRepoId) { + const hasPerItemPullRequestRepoOverride = !!message.pull_request_repo; + + // Skip if agent is already assigned and no explicit per-item pull_request_repo is specified. + // When a different pull_request_repo is provided on the message, allow re-assignment + // so Copilot can be triggered for a different target repository on the same issue. + if (currentAssignees.some(a => a.id === agentId) && !hasPerItemPullRequestRepoOverride) { core.info(`${agentName} is already assigned to ${type} #${number}`); _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, success: true }); return { success: true }; diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs index 28e7ff2acd2..2492d7b5c94 100644 --- a/actions/setup/js/assign_to_agent.test.cjs +++ b/actions/setup/js/assign_to_agent.test.cjs @@ -458,6 +458,57 @@ describe("assign_to_agent", () => { expect(lastGraphQLCall[1].targetRepoId).toBe("other-platform-repo-id"); }); + it("should still skip when agent is already assigned with global pull-request-repo but no per-item override", async () => { + process.env.GH_AW_AGENT_PULL_REQUEST_REPO = "test-owner/global-pr-repo"; + setAgentOutput({ + items: [ + { + type: "assign_to_agent", + issue_number: 42, + agent: "copilot", + }, + ], + errors: [], + }); + + // Mock GraphQL responses + mockGithub.graphql + // Get global PR repository ID and default branch + .mockResolvedValueOnce({ + repository: { + id: "global-pr-repo-id", + defaultBranchRef: { name: "main" }, + }, + }) + // Find agent + .mockResolvedValueOnce({ + repository: { + suggestedActors: { + nodes: [{ login: "copilot-swe-agent", id: "agent-id" }], + }, + }, + }) + // Get issue details - agent is already assigned + .mockResolvedValueOnce({ + repository: { + issue: { + id: "issue-id", + assignees: { + nodes: [{ id: "agent-id", login: "copilot-swe-agent" }], + }, + }, + }, + }); + + await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`); + + // Should see "already assigned" skip message + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #42")); + // Should NOT have called the assignment mutation (only 3 GraphQL calls: repo lookup, find agent, get issue) + expect(mockGithub.graphql).toHaveBeenCalledTimes(3); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + }); + it("should handle API errors gracefully", async () => { setAgentOutput({ items: [ diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 5f582cd2335..c7c2b1fb2ee 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -737,7 +737,7 @@ func buildNotDispatchOrEmptyOperation() ConditionNode { // buildNotForkAndScheduledOrOperation creates a condition for jobs that run on // schedule (or empty operation) AND when a specific operation is selected. -// Condition: !fork && (not_dispatch || operation == ” || operation == op) +// Condition: !fork && (not_dispatch || operation == '' || operation == op) func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { return BuildAnd( buildNotForkCondition(), @@ -753,7 +753,7 @@ func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { // buildRunOperationCondition creates the condition for the unified run_operation // job that handles all dispatch operations except the ones with dedicated jobs. -// Condition: dispatch && operation != ” && operation != each excluded && !fork. +// Condition: dispatch && operation != '' && operation != each excluded && !fork. func buildRunOperationCondition(excludedOperations ...string) ConditionNode { // Start with: event is workflow_dispatch AND operation is not empty condition := BuildAnd(