From 0349cc40158299106035666422c603e2e0b53caf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:01:24 +0000 Subject: [PATCH 1/7] Initial plan From a2db4d401024b2b5b81ec932767f768cdb72baea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:14:19 +0000 Subject: [PATCH 2/7] fix: use fractional rate-limit check for title dedup search guard The TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING = 500 threshold was compared against resources.search.remaining, which has a max of 30/min. This made the guard always trigger, silently skipping dedup on every run. Replace the absolute constant with TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION = 0.2 and compare remaining/limit proportionally. Also read both remaining and limit from the search resource so the check stays correct on GHES where admins can tune ceilings. Fixes #48070 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/create_issue.cjs | 12 +++++++----- actions/setup/js/create_issue.test.cjs | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 411b0b12aba..ae343862b4f 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -35,7 +35,7 @@ const ISSUE_FIELD_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/; const RECENTLY_CLOSED_DEDUP_DAYS = 30; const TITLE_DEDUP_SEARCH_PER_PAGE = 100; const TITLE_DEDUP_MAX_SEARCH_PAGES = 2; -const TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING = 500; +const TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION = 0.2; /** * Create a dedicated GitHub client for copilot assignment operations. @@ -494,14 +494,16 @@ async function getRepoTitleDedupCandidates(githubClient, owner, repo) { async function shouldSkipRepoTitleDedupSearch(githubClient, owner, repo) { try { const response = await githubClient.rest.rateLimit.get(); - const rawRemaining = response?.data?.resources?.search?.remaining; + const { remaining: rawRemaining, limit: rawLimit } = response?.data?.resources?.search ?? {}; const remaining = Number(rawRemaining); - if (!Number.isFinite(remaining)) { + const limit = Number(rawLimit); + if (!Number.isFinite(remaining) || !Number.isFinite(limit)) { core.warning(`Could not determine search rate limit remaining for ${owner}/${repo}; proceeding with repo-level title dedup search`); return false; } - if (remaining <= TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING) { - core.warning(`Skipping repo-level title dedup search for ${owner}/${repo}: search rate limit remaining is ${remaining} (threshold <= ${TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_REMAINING})`); + const threshold = limit * TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION; + if (remaining <= threshold) { + core.warning(`Skipping repo-level title dedup search for ${owner}/${repo}: search rate limit remaining is ${remaining}/${limit} (threshold <= ${Math.round(threshold)})`); return true; } } catch (error) { diff --git a/actions/setup/js/create_issue.test.cjs b/actions/setup/js/create_issue.test.cjs index 236b7c7407b..ca1edb4d685 100644 --- a/actions/setup/js/create_issue.test.cjs +++ b/actions/setup/js/create_issue.test.cjs @@ -46,7 +46,8 @@ describe("create_issue", () => { data: { resources: { search: { - remaining: 1000, + limit: 30, + remaining: 30, }, }, }, @@ -680,6 +681,7 @@ describe("create_issue", () => { data: { resources: { search: { + limit: 30, remaining: 1, }, }, From 292028988170963a8718331a751473820b48cf37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:47:38 +0000 Subject: [PATCH 3/7] chore: start pr-finisher pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index 615a51e551e..3fc711d4035 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -49,6 +49,7 @@ Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/optimize-agentic-workflow.md` - `.github/aw/patterns.md` - `.github/aw/pr-reviewer.md` +- `.github/aw/release-workflow.md` - `.github/aw/report.md` - `.github/aw/reuse.md` - `.github/aw/safe-outputs-automation.md` From c1ef37a12046c4bc6000500b366bfcae6ce9a42c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:55:24 +0000 Subject: [PATCH 4/7] fix: address title-dedup review feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/create_issue.cjs | 4 +-- actions/setup/js/create_issue.test.cjs | 47 ++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index ae343862b4f..71040c6643d 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -498,12 +498,12 @@ async function shouldSkipRepoTitleDedupSearch(githubClient, owner, repo) { const remaining = Number(rawRemaining); const limit = Number(rawLimit); if (!Number.isFinite(remaining) || !Number.isFinite(limit)) { - core.warning(`Could not determine search rate limit remaining for ${owner}/${repo}; proceeding with repo-level title dedup search`); + core.warning(`Could not determine search rate limit values for ${owner}/${repo} (remaining=${rawRemaining}, limit=${rawLimit}); proceeding with repo-level title dedup search`); return false; } const threshold = limit * TITLE_DEDUP_MIN_SEARCH_RATE_LIMIT_FRACTION; if (remaining <= threshold) { - core.warning(`Skipping repo-level title dedup search for ${owner}/${repo}: search rate limit remaining is ${remaining}/${limit} (threshold <= ${Math.round(threshold)})`); + core.warning(`Skipping repo-level title dedup search for ${owner}/${repo}: search rate limit remaining is ${remaining}/${limit} (threshold <= ${Math.floor(threshold)})`); return true; } } catch (error) { diff --git a/actions/setup/js/create_issue.test.cjs b/actions/setup/js/create_issue.test.cjs index ca1edb4d685..656bd4dc521 100644 --- a/actions/setup/js/create_issue.test.cjs +++ b/actions/setup/js/create_issue.test.cjs @@ -681,8 +681,8 @@ describe("create_issue", () => { data: { resources: { search: { - limit: 30, - remaining: 1, + limit: 35, + remaining: 7, }, }, }, @@ -703,6 +703,49 @@ describe("create_issue", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping repo-level title dedup search")); }); + it("should not skip repo-level search when search rate limit is just above 20 percent threshold", async () => { + mockGithub.rest.rateLimit.get.mockResolvedValue({ + data: { + resources: { + search: { + limit: 35, + remaining: 8, + }, + }, + }, + }); + + const handler = await main({ + deduplicate_by_title: true, + }); + const result = await handler({ title: "Above threshold title" }); + + expect(result.success).toBe(true); + expect(mockGithub.rest.search.issuesAndPullRequests).toHaveBeenCalled(); + expect(mockCore.warning).not.toHaveBeenCalledWith(expect.stringContaining("Skipping repo-level title dedup search")); + }); + + it("should proceed with repo-level search when search rate limit values are missing", async () => { + mockGithub.rest.rateLimit.get.mockResolvedValue({ + data: { + resources: { + search: { + remaining: 30, + }, + }, + }, + }); + + const handler = await main({ + deduplicate_by_title: true, + }); + const result = await handler({ title: "Missing limit title" }); + + expect(result.success).toBe(true); + expect(mockGithub.rest.search.issuesAndPullRequests).toHaveBeenCalled(); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("remaining=30, limit=undefined")); + }); + it("should reject invalid deduplicate-by-title configuration", async () => { await expect(main({ deduplicate_by_title: "invalid" })).rejects.toThrow("deduplicate-by-title"); await expect(main({ deduplicate_by_title: 101 })).rejects.toThrow("deduplicate-by-title"); From e4c29d03e2d3e49b62e1c7015c031a0b9fc7591b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:28:29 +0000 Subject: [PATCH 5/7] test: add explicit title-dedup threshold boundary coverage Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/create_issue.test.cjs | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/create_issue.test.cjs b/actions/setup/js/create_issue.test.cjs index 656bd4dc521..456dafbe96e 100644 --- a/actions/setup/js/create_issue.test.cjs +++ b/actions/setup/js/create_issue.test.cjs @@ -682,7 +682,7 @@ describe("create_issue", () => { resources: { search: { limit: 35, - remaining: 7, + remaining: 6, }, }, }, @@ -703,13 +703,35 @@ describe("create_issue", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping repo-level title dedup search")); }); + it("should skip repo-level search when search rate limit is exactly at 20 percent threshold", async () => { + mockGithub.rest.rateLimit.get.mockResolvedValue({ + data: { + resources: { + search: { + limit: 30, + remaining: 6, + }, + }, + }, + }); + + const handler = await main({ + deduplicate_by_title: true, + }); + const result = await handler({ title: "At threshold title" }); + + expect(result.success).toBe(true); + expect(mockGithub.rest.search.issuesAndPullRequests).not.toHaveBeenCalled(); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping repo-level title dedup search")); + }); + it("should not skip repo-level search when search rate limit is just above 20 percent threshold", async () => { mockGithub.rest.rateLimit.get.mockResolvedValue({ data: { resources: { search: { - limit: 35, - remaining: 8, + limit: 30, + remaining: 7, }, }, }, From 9c3d39a79f98db567854f5a16d3150e5e1ca95e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:53:45 +0000 Subject: [PATCH 6/7] chore: complete pr-finisher triage pass Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index 3fc711d4035..615a51e551e 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -49,7 +49,6 @@ Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/optimize-agentic-workflow.md` - `.github/aw/patterns.md` - `.github/aw/pr-reviewer.md` -- `.github/aw/release-workflow.md` - `.github/aw/report.md` - `.github/aw/reuse.md` - `.github/aw/safe-outputs-automation.md` From 274a4a453a09dc65eb8649e831918fe1d727fdcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:58:08 +0000 Subject: [PATCH 7/7] chore: revert unintended skill list change Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index 615a51e551e..3fc711d4035 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -49,6 +49,7 @@ Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/optimize-agentic-workflow.md` - `.github/aw/patterns.md` - `.github/aw/pr-reviewer.md` +- `.github/aw/release-workflow.md` - `.github/aw/report.md` - `.github/aw/reuse.md` - `.github/aw/safe-outputs-automation.md`