From ca14dfc9ef59c119c6bf4db27b632708e2baa695 Mon Sep 17 00:00:00 2001 From: idy Date: Tue, 4 Aug 2026 07:33:16 +0800 Subject: [PATCH] workflows: collect every bounded closing Issue Large tracking pull requests were falsely blocked because the reviewer fetched twenty native closing Issues and retained only ten, while GitHub reported the complete relationship count. - Collect and verify up to GitHub's full 100-node closing-Issue page - Preserve fail-closed behavior when relationship or sub-issue evidence exceeds the bound - Cover a 32-Issue tracker and the former first-ten truncation boundary - Document the complete bounded collection contract Generated with [Codex](https://github.com/openai) --- .github/scripts/pr-readiness/test.mjs | 72 +++++++++++++++++++++++ .github/scripts/pr-readiness/verify.mjs | 3 +- .github/workflows/codex-openai-review.yml | 3 +- README.md | 5 ++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/.github/scripts/pr-readiness/test.mjs b/.github/scripts/pr-readiness/test.mjs index 0f9ffab..bc20033 100644 --- a/.github/scripts/pr-readiness/test.mjs +++ b/.github/scripts/pr-readiness/test.mjs @@ -55,7 +55,50 @@ const context = { const blockerCodes = (readiness) => ( readiness.deterministic_blockers.map((item) => item.code) ); +const closingChildren = Array.from({ length: 31 }, (_, index) => ({ + ...input.linked_issues[0], + number: 101 + index, + parent_number: 100, +})); +const manyLinkedIssues = [{ + ...input.linked_issues[0], + number: 100, + issue_type: "Task", + body: taskBody, + sub_issue_count: closingChildren.length, + sub_issues: closingChildren.map((issue) => ({ + repository: issue.repository, + number: issue.number, + state: issue.state, + })), +}, ...closingChildren]; +const manyLinkedIssuesInput = { + ...input, + linked_issues: manyLinkedIssues, + linked_issue_count: manyLinkedIssues.length, +}; +const scriptDirectory = path.dirname(new URL(import.meta.url).pathname); +const verifySource = fs.readFileSync( + path.join(scriptDirectory, "verify.mjs"), + "utf8", +); +const workflowSource = fs.readFileSync( + path.join(scriptDirectory, "..", "..", "workflows", "codex-openai-review.yml"), + "utf8", +); +for (const source of [verifySource, workflowSource]) { + assert.match(source, /closingIssuesReferences\(first: 100\)/); + assert.doesNotMatch(source, /closingIssuesReferences\.nodes\s*\.slice\(/); +} assert.deepEqual(context.readiness.deterministic_blockers, []); +assert.deepEqual( + analyzePullRequest(manyLinkedIssuesInput).deterministic_blockers, + [], +); +assert.ok(blockerCodes(analyzePullRequest({ + ...manyLinkedIssuesInput, + linked_issue_count: manyLinkedIssues.length + 1, +})).includes("too-many-closing-issues")); assert.ok(analyzePullRequest({ ...input, title: "Bad title" }) .deterministic_blockers.some((item) => item.code === "invalid-title")); assert.ok(analyzePullRequest({ ...input, body: "" }) @@ -427,6 +470,35 @@ try { new RegExp(context.readiness.snapshot_sha256), ); + const manyFixture = structuredClone(fixture); + manyFixture.repository.pullRequest.closingIssuesReferences = { + totalCount: manyLinkedIssues.length, + nodes: manyLinkedIssues.map((issue) => ({ + repository: { nameWithOwner: issue.repository }, + number: issue.number, + title: issue.title, + body: issue.body, + state: issue.state, + issueType: { name: issue.issue_type }, + parent: issue.parent_number == null + ? null + : { number: issue.parent_number }, + subIssues: { + totalCount: issue.sub_issue_count ?? 0, + nodes: (issue.sub_issues ?? []).map((subIssue) => ({ + repository: { nameWithOwner: subIssue.repository }, + number: subIssue.number, + state: subIssue.state, + })), + }, + })), + }; + fs.writeFileSync(fixtureFile, JSON.stringify(manyFixture)); + const manyReadiness = analyzePullRequest(manyLinkedIssuesInput); + const manyResult = verify(manyReadiness.snapshot_sha256); + assert.equal(manyResult.status, 0, manyResult.stderr); + fs.writeFileSync(fixtureFile, JSON.stringify(fixture)); + const assertStale = (mutate) => { const staleFixture = structuredClone(fixture); mutate(staleFixture.repository.pullRequest); diff --git a/.github/scripts/pr-readiness/verify.mjs b/.github/scripts/pr-readiness/verify.mjs index 7d9e999..6c431c6 100644 --- a/.github/scripts/pr-readiness/verify.mjs +++ b/.github/scripts/pr-readiness/verify.mjs @@ -40,7 +40,7 @@ async function fetchPullRequest() { body baseRefOid headRefOid - closingIssuesReferences(first: 20) { + closingIssuesReferences(first: 100) { totalCount nodes { repository { nameWithOwner } @@ -100,7 +100,6 @@ const data = await fetchPullRequest(); const pullRequest = data.repository?.pullRequest; if (!pullRequest) throw new Error("Pull request was not found"); const linkedIssues = pullRequest.closingIssuesReferences.nodes - .slice(0, 10) .map((issue) => ({ repository: issue.repository.nameWithOwner, number: issue.number, diff --git a/.github/workflows/codex-openai-review.yml b/.github/workflows/codex-openai-review.yml index 3bd474a..c39f994 100644 --- a/.github/workflows/codex-openai-review.yml +++ b/.github/workflows/codex-openai-review.yml @@ -392,7 +392,7 @@ jobs: pullRequest(number: $number) { title body - closingIssuesReferences(first: 20) { + closingIssuesReferences(first: 100) { totalCount nodes { repository { nameWithOwner } @@ -449,7 +449,6 @@ jobs: linked_issue_count: pullRequest.closingIssuesReferences.totalCount, linked_issues: pullRequest.closingIssuesReferences.nodes - .slice(0, 10) .map((issue) => ({ repository: issue.repository.nameWithOwner, number: issue.number, diff --git a/README.md b/README.md index b93f655..4082c8a 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,11 @@ linkage requirements: - The complete PR result follows the current Issue plan. Material deviations must be reflected in the Issue or disclosed and resolved in the PR. +The reviewer preserves every native closing Issue returned by GitHub's maximum +100-node GraphQL page. It fails closed when `totalCount` exceeds the collected +nodes, rather than silently reviewing a truncated relationship set. Native +sub-issue snapshots use the same 100-node fail-closed bound. + Caller policy can add trusted organization-level constraints. Repository-owned title formats, Issue Types, sections, relationships, ownership rules, validation commands, platform requirements, and finding severity rules belong