Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions actions/setup/js/assign_to_agent.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,12 @@ async function main(config = {}) {

core.info(`${type} ID: ${assignableId}`);

// Skip if agent is already assigned
if (currentAssignees.some(a => a.id === agentId)) {
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 };
Expand Down
121 changes: 121 additions & 0 deletions actions/setup/js/assign_to_agent.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,127 @@ 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",
Comment on lines +391 to +399
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test covers the per-item pull_request_repo override case, but it doesn’t cover the regression risk introduced by the updated dedup guard when a global pull-request-repo is configured and no per-item override is present. Add a test asserting that when the agent is already assigned and pull_request_repo is not provided, the handler still logs the "already assigned" message and does not call the assignment mutation, even if GH_AW_AGENT_PULL_REQUEST_REPO is set.

Copilot uses AI. Check for mistakes.
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 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: [
Expand Down
Loading