diff --git a/.agents/skills/pr-gardening/scripts/find-candidates.mjs b/.agents/skills/pr-gardening/scripts/find-candidates.mjs index 616ccb2e8c8..142cabe78ef 100755 --- a/.agents/skills/pr-gardening/scripts/find-candidates.mjs +++ b/.agents/skills/pr-gardening/scripts/find-candidates.mjs @@ -33,7 +33,7 @@ export async function findCandidates(options) { const matchesPerIssue = 200; const issueMap = new Map(); let offset = 0; - let truncated = false; + const truncatedIssues = []; while (true) { const query = new URLSearchParams({ @@ -47,12 +47,16 @@ export async function findCandidates(options) { }); const page = await getPaperclip(`/companies/${companyId}/search/extract?${query}`, { apiUrl, apiKey }); for (const issue of page.results) issueMap.set(issue.issueId, issue); - truncated ||= page.results.some((issue) => issue.matchesTruncated); + for (const issue of page.results) { + if (issue.matchesTruncated) truncatedIssues.push({ issueId: issue.issueId, identifier: issue.identifier }); + } if (!page.hasMore) break; offset += limit; if (offset > 5000) throw new Error("Extract-search pagination exceeded the supported 5000 issue offset"); } - if (truncated) throw new Error("Extract-search truncated one or more issue match sets; refusing an incomplete candidate report"); + if (truncatedIssues.length > 0) { + console.warn(`Warning: ${truncatedIssues.length} issue(s) had truncated match sets (>${matchesPerIssue} PR URL matches). These issues may contribute incomplete PR discovery: ${truncatedIssues.map((i) => i.identifier ?? i.issueId).join(", ")}. Proceeding with available data.`); + } const pullRequests = new Map(); for (const issue of issueMap.values()) { @@ -148,7 +152,8 @@ export async function findCandidates(options) { openPullRequestCount: candidates.length, droppedClosedPullRequests: closed, droppedUnavailablePullRequests: unavailable, - truncated: false, + truncatedIssues, + truncated: truncatedIssues.length > 0, }, candidates, }; diff --git a/.agents/skills/pr-gardening/scripts/pr-gardening.test.mjs b/.agents/skills/pr-gardening/scripts/pr-gardening.test.mjs index f697635cccb..f394314dce4 100644 --- a/.agents/skills/pr-gardening/scripts/pr-gardening.test.mjs +++ b/.agents/skills/pr-gardening/scripts/pr-gardening.test.mjs @@ -94,6 +94,55 @@ test("candidate discovery deduplicates mentions and drops closed PRs", async () assert.deepEqual(result.source.droppedUnavailablePullRequests.map((pullRequest) => pullRequest.number), [3]); }); +test("candidate discovery reports truncated issue matches without discarding the report", async () => { + const warnings = []; + const originalWarn = console.warn; + console.warn = (message) => warnings.push(message); + + try { + const result = await findCandidates({ + repo: "paperclipai/paperclip", + api_url: "http://paperclip.test", + api_key: "test-key", + company_id: "company-1", + paperclip_get: async (path) => { + if (path.includes("search/extract")) { + return { + hasMore: false, + results: [ + { + issueId: "issue-truncated", + identifier: "PAP-TRUNCATED", + title: "Large PR thread", + status: "done", + assigneeAgentId: "agent-1", + updatedAt: "2026-07-13T00:00:00Z", + matchesTruncated: true, + matches: [], + }, + ], + }; + } + throw new Error(`Unexpected Paperclip path: ${path}`); + }, + gh_json: async () => { + throw new Error("GitHub should not be queried without discovered pull requests"); + }, + }); + + assert.equal(result.source.truncated, true); + assert.deepEqual(result.source.truncatedIssues, [ + { issueId: "issue-truncated", identifier: "PAP-TRUNCATED" }, + ]); + assert.deepEqual(result.candidates, []); + assert.equal(warnings.length, 1); + assert.match(warnings[0], /PAP-TRUNCATED/); + assert.match(warnings[0], /Proceeding with available data/); + } finally { + console.warn = originalWarn; + } +}); + test("missing-PR detection matches only deleted/nonexistent PR signals", () => { // gh's real signals for a deleted/nonexistent PR: GraphQL resolution failure and REST 404. assert.equal(isMissingPullRequestError(new Error("GraphQL: Could not resolve to a PullRequest with the number of 3")), true);