Skip to content
Closed
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
7 changes: 5 additions & 2 deletions actions/setup/js/assign_to_agent.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,11 @@ async function main() {

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

// Check if agent is already assigned
if (currentAssignees.some(a => a.id === agentId)) {
// Check if agent is already assigned.
// When a pull_request_repo is explicitly provided, the pair of (agent, pull_request_repo)
// is the uniqueness key, so we allow the assignment to proceed even if the agent is
// already assigned (it may target a different repository).
if (currentAssignees.some(a => a.id === agentId) && !effectivePullRequestRepoId) {
Comment on lines +451 to +455

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

The updated guard disables the “already assigned” short-circuit whenever effectivePullRequestRepoId is set, but it doesn’t actually enforce uniqueness on (agent, pull_request_repo). As a result, if the same issue appears multiple times with the same pull_request_repo in a single run, this will re-run the mutation each time and can trigger duplicate agent runs/PR attempts. Consider tracking attempted assignments in-memory using a key like (assignableId, agentId, effectivePullRequestRepoId || "") and skipping when the same key is seen again, while still allowing a different pull_request_repo to proceed.

Copilot uses AI. Check for mistakes.
core.info(`${agentName} is already assigned to ${type} #${number}`);
results.push({
issue_number: issueNumber,
Expand Down
61 changes: 61 additions & 0 deletions actions/setup/js/assign_to_agent.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,67 @@ describe("assign_to_agent", () => {
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #42"));
});

it("should allow assignment when agent is already assigned but pull_request_repo differs", async () => {
// When an agent is already assigned to an issue but a different pull_request_repo is requested,
// the assignment must proceed so that Copilot is triggered for the new target repository.
process.env.GH_AW_AGENT_ALLOWED_PULL_REQUEST_REPOS = "test-owner/android-repo";
setAgentOutput({
items: [
{
type: "assign_to_agent",
issue_number: 42,
agent: "copilot",
pull_request_repo: "test-owner/android-repo",
},
],
errors: [],
});

// Mock GraphQL responses - agent is already assigned, but a different pull_request_repo is provided
mockGithub.graphql
// Get item PR repository ID
.mockResolvedValueOnce({
repository: {
id: "android-repo-id",
},
})
// Find agent
.mockResolvedValueOnce({
repository: {
suggestedActors: {
nodes: [{ login: "copilot-swe-agent", id: "MDQ6VXNlcjE=" }],
},
},
})
// Get issue details - agent already assigned
.mockResolvedValueOnce({
repository: {
issue: {
id: "issue-id",
assignees: {
nodes: [{ id: "MDQ6VXNlcjE=" }],
},
},
},
})
// Assign agent with agentAssignment (targetRepositoryId = android-repo-id)
.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await eval(`(async () => { ${assignToAgentScript}; await main(); })()`);

// Should NOT skip the assignment
expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #42"));
// Should proceed with the assignment
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Successfully assigned copilot coding agent to issue #42"));
// The mutation must include the target repository ID for the new platform
const lastGraphQLCall = mockGithub.graphql.mock.lastCall;
expect(lastGraphQLCall[1].targetRepoId).toBe("android-repo-id");
});

it("should handle API errors gracefully", async () => {
setAgentOutput({
items: [
Expand Down
Loading