From a49f1ca3540bb1590a256acdb7c91ae941cc930c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:44:22 +0000
Subject: [PATCH 1/6] Initial plan


From a487ae7a7494e01a41ce7b5f3077658b531aedc7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 14:19:05 +0000
Subject: [PATCH 2/6] fix(assign_to_agent): allow repeated issue assignments in
 multi-repo flows

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/35aa1b6a-6d55-47e5-b040-3f7167d9bf21

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 actions/setup/js/assign_to_agent.cjs      |  7 +-
 actions/setup/js/assign_to_agent.test.cjs | 92 +++++++++++++++++++++++
 2 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs
index 8a308cea146..0512d908327 100644
--- a/actions/setup/js/assign_to_agent.cjs
+++ b/actions/setup/js/assign_to_agent.cjs
@@ -128,6 +128,7 @@ async function main(config = {}) {
   // Closure-level state
   let processedCount = 0;
   const agentCache = {};
+  const seenAssignmentTargets = new Set();
 
   // Reset module-level results for this handler invocation
   _allResults = [];
@@ -291,6 +292,9 @@ async function main(config = {}) {
     const type = targetResult.contextType;
     const issueNumber = type === "issue" ? number : null;
     const pullNumber = type === "pull request" ? number : null;
+    const assignmentTargetKey = `${effectiveOwner}/${effectiveRepo}:${type}:${number}`;
+    const seenThisTargetBefore = seenAssignmentTargets.has(assignmentTargetKey);
+    seenAssignmentTargets.add(assignmentTargetKey);
 
     if (isNaN(number) || number <= 0) {
       const error = `Invalid ${type} number: ${number}`;
@@ -352,11 +356,12 @@ async function main(config = {}) {
       core.info(`${type} ID: ${assignableId}`);
 
       const hasPerItemPullRequestRepoOverride = !!message.pull_request_repo;
+      const hasExplicitReassignmentIntent = hasPerItemPullRequestRepoOverride || seenThisTargetBefore;
 
       // 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) {
+      if (currentAssignees.some(a => a.id === agentId) && !hasExplicitReassignmentIntent) {
         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 2492d7b5c94..60aa5b97f07 100644
--- a/actions/setup/js/assign_to_agent.test.cjs
+++ b/actions/setup/js/assign_to_agent.test.cjs
@@ -458,6 +458,98 @@ describe("assign_to_agent", () => {
     expect(lastGraphQLCall[1].targetRepoId).toBe("other-platform-repo-id");
   });
 
+  it("should process multiple assignments for the same temporary issue ID across different pull_request_repo targets", async () => {
+    process.env.GH_AW_AGENT_MAX_COUNT = "5";
+    process.env.GH_AW_TEMPORARY_ID_MAP = JSON.stringify({
+      aw_multi_repo: { repo: "test-owner/test-repo", number: 6587 },
+    });
+    process.env.GH_AW_AGENT_ALLOWED_PULL_REQUEST_REPOS = "test-owner/ios-repo,test-owner/android-repo";
+
+    setAgentOutput({
+      items: [
+        {
+          type: "assign_to_agent",
+          issue_number: "aw_multi_repo",
+          agent: "copilot",
+          pull_request_repo: "test-owner/ios-repo",
+        },
+        {
+          type: "assign_to_agent",
+          issue_number: "aw_multi_repo",
+          agent: "copilot",
+          pull_request_repo: "test-owner/android-repo",
+        },
+      ],
+      errors: [],
+    });
+
+    mockGithub.graphql
+      // Item 1: get per-item PR repository ID
+      .mockResolvedValueOnce({
+        repository: {
+          id: "ios-repo-id",
+        },
+      })
+      // Item 1: find agent
+      .mockResolvedValueOnce({
+        repository: {
+          suggestedActors: {
+            nodes: [{ login: "copilot-swe-agent", id: "agent-id" }],
+          },
+        },
+      })
+      // Item 1: issue details (not assigned yet)
+      .mockResolvedValueOnce({
+        repository: {
+          issue: {
+            id: "issue-id",
+            assignees: {
+              nodes: [],
+            },
+          },
+        },
+      })
+      // Item 1: assignment mutation
+      .mockResolvedValueOnce({
+        replaceActorsForAssignable: {
+          __typename: "ReplaceActorsForAssignablePayload",
+        },
+      })
+      // Item 2: get per-item PR repository ID
+      .mockResolvedValueOnce({
+        repository: {
+          id: "android-repo-id",
+        },
+      })
+      // Item 2: issue details (already assigned after item 1)
+      .mockResolvedValueOnce({
+        repository: {
+          issue: {
+            id: "issue-id",
+            assignees: {
+              nodes: [{ id: "agent-id", login: "copilot-swe-agent" }],
+            },
+          },
+        },
+      })
+      // Item 2: assignment mutation should still run
+      .mockResolvedValueOnce({
+        replaceActorsForAssignable: {
+          __typename: "ReplaceActorsForAssignablePayload",
+        },
+      });
+
+    await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`);
+
+    expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #6587"));
+    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Successfully assigned copilot coding agent to issue #6587"));
+
+    const assignmentCalls = mockGithub.graphql.mock.calls.filter(([query]) => query.includes("replaceActorsForAssignable"));
+    expect(assignmentCalls).toHaveLength(2);
+    expect(assignmentCalls[0][1].targetRepoId).toBe("ios-repo-id");
+    expect(assignmentCalls[1][1].targetRepoId).toBe("android-repo-id");
+  }, 20000);
+
   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({

From 0bef8acde901e9608e996f3ad5c353ae2f6d229f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 14:30:03 +0000
Subject: [PATCH 3/6] Changes before error encountered

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/35aa1b6a-6d55-47e5-b040-3f7167d9bf21

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 actions/setup/js/assign_to_agent.cjs      | 15 +++--
 actions/setup/js/assign_to_agent.test.cjs | 82 +++++++++++++++++++++++
 2 files changed, 91 insertions(+), 6 deletions(-)

diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs
index 0512d908327..725c171c7bb 100644
--- a/actions/setup/js/assign_to_agent.cjs
+++ b/actions/setup/js/assign_to_agent.cjs
@@ -128,7 +128,7 @@ async function main(config = {}) {
   // Closure-level state
   let processedCount = 0;
   const agentCache = {};
-  const seenAssignmentTargets = new Set();
+  const processedAssignmentTargets = new Set();
 
   // Reset module-level results for this handler invocation
   _allResults = [];
@@ -292,9 +292,6 @@ async function main(config = {}) {
     const type = targetResult.contextType;
     const issueNumber = type === "issue" ? number : null;
     const pullNumber = type === "pull request" ? number : null;
-    const assignmentTargetKey = `${effectiveOwner}/${effectiveRepo}:${type}:${number}`;
-    const seenThisTargetBefore = seenAssignmentTargets.has(assignmentTargetKey);
-    seenAssignmentTargets.add(assignmentTargetKey);
 
     if (isNaN(number) || number <= 0) {
       const error = `Invalid ${type} number: ${number}`;
@@ -356,12 +353,18 @@ async function main(config = {}) {
       core.info(`${type} ID: ${assignableId}`);
 
       const hasPerItemPullRequestRepoOverride = !!message.pull_request_repo;
-      const hasExplicitReassignmentIntent = hasPerItemPullRequestRepoOverride || seenThisTargetBefore;
+      const normalizedPullRequestRepo = hasPerItemPullRequestRepoOverride ? String(message.pull_request_repo).trim() : "default";
+      const assignmentContextKey = `${effectiveOwner}/${effectiveRepo}:${type}:${number}:${normalizedPullRequestRepo}`;
+      const seenThisContextBefore = processedAssignmentTargets.has(assignmentContextKey);
+      // Track assignment context (target + per-item pull_request_repo) to prevent duplicate
+      // re-assignment calls while still allowing one global issue to fan out to multiple repos.
+      processedAssignmentTargets.add(assignmentContextKey);
+      const shouldAllowReassignment = hasPerItemPullRequestRepoOverride && !seenThisContextBefore;
 
       // 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) && !hasExplicitReassignmentIntent) {
+      if (currentAssignees.some(a => a.id === agentId) && !shouldAllowReassignment) {
         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 60aa5b97f07..d79f4cd2c09 100644
--- a/actions/setup/js/assign_to_agent.test.cjs
+++ b/actions/setup/js/assign_to_agent.test.cjs
@@ -550,6 +550,88 @@ describe("assign_to_agent", () => {
     expect(assignmentCalls[1][1].targetRepoId).toBe("android-repo-id");
   }, 20000);
 
+  it("should avoid duplicate re-assignment for the same issue and same pull_request_repo in one run", async () => {
+    process.env.GH_AW_AGENT_MAX_COUNT = "5";
+    process.env.GH_AW_TEMPORARY_ID_MAP = JSON.stringify({
+      aw_duplicate: { repo: "test-owner/test-repo", number: 6587 },
+    });
+    process.env.GH_AW_AGENT_ALLOWED_PULL_REQUEST_REPOS = "test-owner/ios-repo";
+
+    setAgentOutput({
+      items: [
+        {
+          type: "assign_to_agent",
+          issue_number: "aw_duplicate",
+          agent: "copilot",
+          pull_request_repo: "test-owner/ios-repo",
+        },
+        {
+          type: "assign_to_agent",
+          issue_number: "aw_duplicate",
+          agent: "copilot",
+          pull_request_repo: "test-owner/ios-repo",
+        },
+      ],
+      errors: [],
+    });
+
+    mockGithub.graphql
+      // Item 1: get per-item PR repository ID
+      .mockResolvedValueOnce({
+        repository: {
+          id: "ios-repo-id",
+        },
+      })
+      // Item 1: find agent
+      .mockResolvedValueOnce({
+        repository: {
+          suggestedActors: {
+            nodes: [{ login: "copilot-swe-agent", id: "agent-id" }],
+          },
+        },
+      })
+      // Item 1: issue details (not assigned yet)
+      .mockResolvedValueOnce({
+        repository: {
+          issue: {
+            id: "issue-id",
+            assignees: {
+              nodes: [],
+            },
+          },
+        },
+      })
+      // Item 1: assignment mutation
+      .mockResolvedValueOnce({
+        replaceActorsForAssignable: {
+          __typename: "ReplaceActorsForAssignablePayload",
+        },
+      })
+      // Item 2: get per-item PR repository ID
+      .mockResolvedValueOnce({
+        repository: {
+          id: "ios-repo-id",
+        },
+      })
+      // Item 2: issue details (already assigned after item 1)
+      .mockResolvedValueOnce({
+        repository: {
+          issue: {
+            id: "issue-id",
+            assignees: {
+              nodes: [{ id: "agent-id", login: "copilot-swe-agent" }],
+            },
+          },
+        },
+      });
+
+    await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`);
+
+    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #6587"));
+    const assignmentCalls = mockGithub.graphql.mock.calls.filter(([query]) => query.includes("replaceActorsForAssignable"));
+    expect(assignmentCalls).toHaveLength(1);
+  }, 20000);
+
   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({

From a1118608b7664b57535f23fa14a048554b9d81ca Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 16:06:25 +0000
Subject: [PATCH 4/6] fix(assign_to_agent): address review feedback for
 multi-repo assignment

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d2b4077d-7102-41a5-930e-a274fbe9e182

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 actions/setup/js/assign_to_agent.cjs      | 35 ++++++-----
 actions/setup/js/assign_to_agent.test.cjs | 71 ++++++++++++++++++++++-
 2 files changed, 89 insertions(+), 17 deletions(-)

diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs
index 725c171c7bb..ed97f5548e2 100644
--- a/actions/setup/js/assign_to_agent.cjs
+++ b/actions/setup/js/assign_to_agent.cjs
@@ -15,7 +15,7 @@ const { sanitizeContent } = require("./sanitize_content.cjs");
  * Module-level state — populated by main(), read by the exported getters below.
  * Using module-level variables (rather than closure-only state) allows the handler
  * manager to read final output values after all messages have been processed.
- * @type {Array<{issue_number: number|null, pull_number: number|null, agent: string, owner: string|null, repo: string|null, success: boolean, skipped?: boolean, error?: string}>}
+ * @type {Array<{issue_number: number|null, pull_number: number|null, agent: string, owner: string|null, repo: string|null, pull_request_repo?: string|null, success: boolean, skipped?: boolean, error?: string}>}
  */
 let _allResults = [];
 
@@ -233,10 +233,15 @@ async function main(config = {}) {
     const hasExplicitTarget = itemForTarget.issue_number != null || itemForTarget.pull_number != null;
     const effectiveTarget = hasExplicitTarget ? "*" : targetConfig;
 
+    const defaultPullRequestRepoSlug = pullRequestOwner && pullRequestRepo ? `${pullRequestOwner}/${pullRequestRepo}` : `${effectiveOwner}/${effectiveRepo}`;
+
     // Handle per-item pull_request_repo override
     let effectivePullRequestRepoId = pullRequestRepoId;
-    if (message.pull_request_repo) {
-      const itemPullRequestRepo = String(message.pull_request_repo).trim();
+    let effectivePullRequestRepoSlug = defaultPullRequestRepoSlug;
+    let hasValidatedPerItemPullRequestRepoOverride = false;
+    const rawPullRequestRepoOverride = typeof message.pull_request_repo === "string" ? message.pull_request_repo.trim() : "";
+    if (rawPullRequestRepoOverride) {
+      const itemPullRequestRepo = rawPullRequestRepoOverride;
       const pullRequestRepoParts = itemPullRequestRepo.split("/");
       if (pullRequestRepoParts.length === 2) {
         const defaultPullRequestRepo = pullRequestRepoConfig || defaultTargetRepo;
@@ -255,6 +260,8 @@ async function main(config = {}) {
           `;
           const itemPullRequestRepoResponse = await githubClient.graphql(itemPullRequestRepoQuery, { owner: pullRequestRepoParts[0], name: pullRequestRepoParts[1] });
           effectivePullRequestRepoId = itemPullRequestRepoResponse.repository.id;
+          effectivePullRequestRepoSlug = itemPullRequestRepo;
+          hasValidatedPerItemPullRequestRepoOverride = true;
           core.info(`Using per-item pull request repository: ${itemPullRequestRepo} (ID: ${effectivePullRequestRepoId})`);
         } catch (error) {
           const errorMsg = `Failed to fetch pull request repository ID for ${itemPullRequestRepo}: ${getErrorMessage(error)}`;
@@ -265,6 +272,8 @@ async function main(config = {}) {
       } else {
         core.warning(`Invalid pull_request_repo format: ${itemPullRequestRepo}. Expected owner/repo. Using global pull-request-repo if configured.`);
       }
+    } else if (message.pull_request_repo != null && message.pull_request_repo !== "") {
+      core.warning("Invalid pull_request_repo value. Expected a non-empty owner/repo string. Using global pull-request-repo if configured.");
     }
 
     // Resolve the target issue or pull request number from context
@@ -352,21 +361,19 @@ async function main(config = {}) {
 
       core.info(`${type} ID: ${assignableId}`);
 
-      const hasPerItemPullRequestRepoOverride = !!message.pull_request_repo;
-      const normalizedPullRequestRepo = hasPerItemPullRequestRepoOverride ? String(message.pull_request_repo).trim() : "default";
-      const assignmentContextKey = `${effectiveOwner}/${effectiveRepo}:${type}:${number}:${normalizedPullRequestRepo}`;
+      const assignmentContextKey = `${effectiveOwner}/${effectiveRepo}:${type}:${number}:${effectivePullRequestRepoSlug}`;
       const seenThisContextBefore = processedAssignmentTargets.has(assignmentContextKey);
       // Track assignment context (target + per-item pull_request_repo) to prevent duplicate
       // re-assignment calls while still allowing one global issue to fan out to multiple repos.
       processedAssignmentTargets.add(assignmentContextKey);
-      const shouldAllowReassignment = hasPerItemPullRequestRepoOverride && !seenThisContextBefore;
+      const shouldAllowReassignment = hasValidatedPerItemPullRequestRepoOverride && !seenThisContextBefore;
 
       // 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) && !shouldAllowReassignment) {
         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 });
+        _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, pull_request_repo: effectivePullRequestRepoSlug, success: true });
         return { success: true };
       }
 
@@ -380,7 +387,7 @@ async function main(config = {}) {
       if (!success) throw new Error(`Failed to assign ${agentName} via GraphQL`);
 
       core.info(`Successfully assigned ${agentName} coding agent to ${type} #${number}`);
-      _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, success: true });
+      _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, pull_request_repo: effectivePullRequestRepoSlug, success: true });
       return { success: true };
     } catch (error) {
       let errorMessage = getErrorMessage(error);
@@ -390,7 +397,7 @@ async function main(config = {}) {
       if (ignoreIfError && isAuthError) {
         core.warning(`Agent assignment failed for ${agentName} on ${type} #${number} due to authentication/permission error. Skipping due to ignore-if-error=true.`);
         core.info(`Error details: ${errorMessage}`);
-        _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, success: true, skipped: true });
+        _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, pull_request_repo: effectivePullRequestRepoSlug, success: true, skipped: true });
         return { success: true, skipped: true };
       }
 
@@ -418,7 +425,7 @@ async function main(config = {}) {
         core.warning(`Failed to post failure comment on ${type} #${number}: ${getErrorMessage(commentError)}`);
       }
 
-      _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, success: false, error: errorMessage });
+      _allResults.push({ issue_number: issueNumber, pull_number: pullNumber, agent: agentName, owner: effectiveOwner, repo: effectiveRepo, pull_request_repo: effectivePullRequestRepoSlug, success: false, error: errorMessage });
       return { success: false, error: errorMessage };
     }
   };
@@ -483,7 +490,7 @@ async function writeAssignToAgentSummary() {
     summaryContent += successResults
       .map(r => {
         const itemType = r.issue_number ? `Issue #${r.issue_number}` : `Pull Request #${r.pull_number}`;
-        return `- ${itemType} → Agent: ${r.agent}`;
+        return `- ${itemType} → Agent: ${r.agent}${r.pull_request_repo ? ` (PR target: ${r.pull_request_repo})` : ""}`;
       })
       .join("\n");
     summaryContent += "\n\n";
@@ -494,7 +501,7 @@ async function writeAssignToAgentSummary() {
     summaryContent += skippedResults
       .map(r => {
         const itemType = r.issue_number ? `Issue #${r.issue_number}` : `Pull Request #${r.pull_number}`;
-        return `- ${itemType} → Agent: ${r.agent} (assignment failed due to error)`;
+        return `- ${itemType} → Agent: ${r.agent}${r.pull_request_repo ? ` (PR target: ${r.pull_request_repo})` : ""} (assignment failed due to error)`;
       })
       .join("\n");
     summaryContent += "\n\n";
@@ -505,7 +512,7 @@ async function writeAssignToAgentSummary() {
     summaryContent += failedResults
       .map(r => {
         const itemType = r.issue_number ? `Issue #${r.issue_number}` : `Pull Request #${r.pull_number}`;
-        return `- ${itemType} → Agent: ${r.agent}: ${r.error}`;
+        return `- ${itemType} → Agent: ${r.agent}${r.pull_request_repo ? ` (PR target: ${r.pull_request_repo})` : ""}: ${r.error}`;
       })
       .join("\n");
 
diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs
index d79f4cd2c09..77ff8362689 100644
--- a/actions/setup/js/assign_to_agent.test.cjs
+++ b/actions/setup/js/assign_to_agent.test.cjs
@@ -38,6 +38,8 @@ global.github = mockGithub;
 describe("assign_to_agent", () => {
   let assignToAgentScript;
   let tempFilePath;
+  let errorRecoveryModulePath;
+  const mockSleep = vi.fn().mockResolvedValue();
 
   // Simulates the safe-output handler manager: builds handler config from env vars,
   // calls main() as a factory, then processes items from GH_AW_AGENT_OUTPUT.
@@ -92,6 +94,7 @@ describe("assign_to_agent", () => {
 
   beforeEach(() => {
     vi.clearAllMocks();
+    mockSleep.mockClear();
 
     // Reset mockGithub.graphql to ensure no lingering mock implementations
     mockGithub.graphql = vi.fn();
@@ -122,6 +125,14 @@ describe("assign_to_agent", () => {
     // Clear module cache to ensure we get the latest version of assign_agent_helpers
     const helpersPath = require.resolve("./assign_agent_helpers.cjs");
     delete require.cache[helpersPath];
+    errorRecoveryModulePath = require.resolve("./error_recovery.cjs");
+    delete require.cache[errorRecoveryModulePath];
+    require.cache[errorRecoveryModulePath] = {
+      id: errorRecoveryModulePath,
+      filename: errorRecoveryModulePath,
+      loaded: true,
+      exports: { sleep: mockSleep },
+    };
 
     const scriptPath = path.join(process.cwd(), "assign_to_agent.cjs");
     assignToAgentScript = fs.readFileSync(scriptPath, "utf8");
@@ -131,6 +142,9 @@ describe("assign_to_agent", () => {
     if (tempFilePath && fs.existsSync(tempFilePath)) {
       fs.unlinkSync(tempFilePath);
     }
+    if (errorRecoveryModulePath) {
+      delete require.cache[errorRecoveryModulePath];
+    }
   });
 
   it("should handle empty agent output", async () => {
@@ -548,7 +562,13 @@ describe("assign_to_agent", () => {
     expect(assignmentCalls).toHaveLength(2);
     expect(assignmentCalls[0][1].targetRepoId).toBe("ios-repo-id");
     expect(assignmentCalls[1][1].targetRepoId).toBe("android-repo-id");
-  }, 20000);
+    expect(mockSleep).toHaveBeenCalledTimes(1);
+    expect(mockSleep).toHaveBeenCalledWith(10000);
+
+    const summaryCall = mockCore.summary.addRaw.mock.calls[0][0];
+    expect(summaryCall).toContain("PR target: test-owner/ios-repo");
+    expect(summaryCall).toContain("PR target: test-owner/android-repo");
+  });
 
   it("should avoid duplicate re-assignment for the same issue and same pull_request_repo in one run", async () => {
     process.env.GH_AW_AGENT_MAX_COUNT = "5";
@@ -630,7 +650,50 @@ describe("assign_to_agent", () => {
     expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #6587"));
     const assignmentCalls = mockGithub.graphql.mock.calls.filter(([query]) => query.includes("replaceActorsForAssignable"));
     expect(assignmentCalls).toHaveLength(1);
-  }, 20000);
+    expect(mockSleep).toHaveBeenCalledTimes(1);
+    expect(mockSleep).toHaveBeenCalledWith(10000);
+  });
+
+  it("should not treat whitespace pull_request_repo as a reassignment override", async () => {
+    setAgentOutput({
+      items: [
+        {
+          type: "assign_to_agent",
+          issue_number: 42,
+          agent: "copilot",
+          pull_request_repo: "   ",
+        },
+      ],
+      errors: [],
+    });
+
+    mockGithub.graphql
+      // Find agent
+      .mockResolvedValueOnce({
+        repository: {
+          suggestedActors: {
+            nodes: [{ login: "copilot-swe-agent", id: "agent-id" }],
+          },
+        },
+      })
+      // Get issue details - already assigned
+      .mockResolvedValueOnce({
+        repository: {
+          issue: {
+            id: "issue-id",
+            assignees: {
+              nodes: [{ id: "agent-id", login: "copilot-swe-agent" }],
+            },
+          },
+        },
+      });
+
+    await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`);
+
+    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("copilot is already assigned to issue #42"));
+    const assignmentCalls = mockGithub.graphql.mock.calls.filter(([query]) => query.includes("replaceActorsForAssignable"));
+    expect(assignmentCalls).toHaveLength(0);
+  });
 
   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";
@@ -1530,7 +1593,9 @@ describe("assign_to_agent", () => {
     // Verify delay message was logged twice (2 delays between 3 items)
     const delayMessages = mockCore.info.mock.calls.filter(call => call[0].includes("Waiting 10 seconds before processing next agent assignment"));
     expect(delayMessages).toHaveLength(2);
-  }, 30000); // Increase timeout to 30 seconds to account for 2x10s delays
+    expect(mockSleep).toHaveBeenCalledTimes(2);
+    expect(mockSleep).toHaveBeenCalledWith(10000);
+  });
 
   describe("Cross-repository allowlist validation", () => {
     it("should reject target repository not in allowlist", async () => {

From 5b6d98587d388a6b0404cdd475b9ccfc610ba718 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 16:09:33 +0000
Subject: [PATCH 5/6] test(assign_to_agent): remove real delays and harden
 per-item repo override handling

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d2b4077d-7102-41a5-930e-a274fbe9e182

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 | 16 ++++------------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs
index ed97f5548e2..1d272f702eb 100644
--- a/actions/setup/js/assign_to_agent.cjs
+++ b/actions/setup/js/assign_to_agent.cjs
@@ -233,11 +233,11 @@ async function main(config = {}) {
     const hasExplicitTarget = itemForTarget.issue_number != null || itemForTarget.pull_number != null;
     const effectiveTarget = hasExplicitTarget ? "*" : targetConfig;
 
-    const defaultPullRequestRepoSlug = pullRequestOwner && pullRequestRepo ? `${pullRequestOwner}/${pullRequestRepo}` : `${effectiveOwner}/${effectiveRepo}`;
+    const basePullRequestRepoSlug = pullRequestOwner && pullRequestRepo ? `${pullRequestOwner}/${pullRequestRepo}` : `${effectiveOwner}/${effectiveRepo}`;
 
     // Handle per-item pull_request_repo override
     let effectivePullRequestRepoId = pullRequestRepoId;
-    let effectivePullRequestRepoSlug = defaultPullRequestRepoSlug;
+    let effectivePullRequestRepoSlug = basePullRequestRepoSlug;
     let hasValidatedPerItemPullRequestRepoOverride = false;
     const rawPullRequestRepoOverride = typeof message.pull_request_repo === "string" ? message.pull_request_repo.trim() : "";
     if (rawPullRequestRepoOverride) {
@@ -272,6 +272,8 @@ async function main(config = {}) {
       } else {
         core.warning(`Invalid pull_request_repo format: ${itemPullRequestRepo}. Expected owner/repo. Using global pull-request-repo if configured.`);
       }
+    } else if (typeof message.pull_request_repo === "string" && message.pull_request_repo.trim() === "") {
+      core.warning("Invalid pull_request_repo value. Expected owner/repo. Using global pull-request-repo if configured.");
     } else if (message.pull_request_repo != null && message.pull_request_repo !== "") {
       core.warning("Invalid pull_request_repo value. Expected a non-empty owner/repo string. Using global pull-request-repo if configured.");
     }
diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs
index 77ff8362689..95de8eb3977 100644
--- a/actions/setup/js/assign_to_agent.test.cjs
+++ b/actions/setup/js/assign_to_agent.test.cjs
@@ -38,7 +38,7 @@ global.github = mockGithub;
 describe("assign_to_agent", () => {
   let assignToAgentScript;
   let tempFilePath;
-  let errorRecoveryModulePath;
+  let sleepSpy;
   const mockSleep = vi.fn().mockResolvedValue();
 
   // Simulates the safe-output handler manager: builds handler config from env vars,
@@ -125,14 +125,8 @@ describe("assign_to_agent", () => {
     // Clear module cache to ensure we get the latest version of assign_agent_helpers
     const helpersPath = require.resolve("./assign_agent_helpers.cjs");
     delete require.cache[helpersPath];
-    errorRecoveryModulePath = require.resolve("./error_recovery.cjs");
-    delete require.cache[errorRecoveryModulePath];
-    require.cache[errorRecoveryModulePath] = {
-      id: errorRecoveryModulePath,
-      filename: errorRecoveryModulePath,
-      loaded: true,
-      exports: { sleep: mockSleep },
-    };
+    const errorRecovery = require("./error_recovery.cjs");
+    sleepSpy = vi.spyOn(errorRecovery, "sleep").mockImplementation(mockSleep);
 
     const scriptPath = path.join(process.cwd(), "assign_to_agent.cjs");
     assignToAgentScript = fs.readFileSync(scriptPath, "utf8");
@@ -142,9 +136,7 @@ describe("assign_to_agent", () => {
     if (tempFilePath && fs.existsSync(tempFilePath)) {
       fs.unlinkSync(tempFilePath);
     }
-    if (errorRecoveryModulePath) {
-      delete require.cache[errorRecoveryModulePath];
-    }
+    sleepSpy?.mockRestore();
   });
 
   it("should handle empty agent output", async () => {

From c99393d5daab014c75c70e3237770d900b6270c6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Apr 2026 16:10:49 +0000
Subject: [PATCH 6/6] refactor(assign_to_agent): clarify pull_request_repo
 override validation

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d2b4077d-7102-41a5-930e-a274fbe9e182

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 actions/setup/js/assign_to_agent.cjs | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs
index 1d272f702eb..7c6ad87d590 100644
--- a/actions/setup/js/assign_to_agent.cjs
+++ b/actions/setup/js/assign_to_agent.cjs
@@ -239,9 +239,10 @@ async function main(config = {}) {
     let effectivePullRequestRepoId = pullRequestRepoId;
     let effectivePullRequestRepoSlug = basePullRequestRepoSlug;
     let hasValidatedPerItemPullRequestRepoOverride = false;
-    const rawPullRequestRepoOverride = typeof message.pull_request_repo === "string" ? message.pull_request_repo.trim() : "";
-    if (rawPullRequestRepoOverride) {
-      const itemPullRequestRepo = rawPullRequestRepoOverride;
+    const hasPullRequestRepoOverrideField = message.pull_request_repo != null;
+    const trimmedPullRequestRepoOverride = typeof message.pull_request_repo === "string" ? message.pull_request_repo.trim() : "";
+    if (trimmedPullRequestRepoOverride) {
+      const itemPullRequestRepo = trimmedPullRequestRepoOverride;
       const pullRequestRepoParts = itemPullRequestRepo.split("/");
       if (pullRequestRepoParts.length === 2) {
         const defaultPullRequestRepo = pullRequestRepoConfig || defaultTargetRepo;
@@ -272,9 +273,9 @@ async function main(config = {}) {
       } else {
         core.warning(`Invalid pull_request_repo format: ${itemPullRequestRepo}. Expected owner/repo. Using global pull-request-repo if configured.`);
       }
-    } else if (typeof message.pull_request_repo === "string" && message.pull_request_repo.trim() === "") {
+    } else if (hasPullRequestRepoOverrideField && typeof message.pull_request_repo === "string") {
       core.warning("Invalid pull_request_repo value. Expected owner/repo. Using global pull-request-repo if configured.");
-    } else if (message.pull_request_repo != null && message.pull_request_repo !== "") {
+    } else if (hasPullRequestRepoOverrideField) {
       core.warning("Invalid pull_request_repo value. Expected a non-empty owner/repo string. Using global pull-request-repo if configured.");
     }
 
