Repository navigation
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅
|
|
✅ Test Quality Sentinel completed test quality analysis. Test Quality SentinelOutcome: The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible. Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft. Design Decision Gate 🏗️Outcome: The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible. Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.
|
|
✅ Ponytail Reviewer completed successfully! Ponytail ReviewerOutcome: The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible. Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.
|
|
✅ PR Code Quality Reviewer completed the code quality review.
|
There was a problem hiding this comment.
🟡 Changes recommended
The case-sensitive self-duplicate guard allows mixed-case references to the same repository and issue, resulting in an invalid GitHub request.
1 open issue
What changed in this PR
This PR addresses #73054 by resolving canonical issue references to REST database IDs for duplicate-close intents, allowing the API to create pending suggestions for maintainer review.
Changes:
- Include
duplicate_issue_idin eligible duplicate-close intent requests. - Propagate canonical lookup failures without falling back to a legacy close.
- Add regression coverage for reference formats, cross-repository resolution, lookup failures, and ineligible references.
| File | Description |
|---|---|
actions/setup/js/close_issue.test.cjs |
Adds canonical-ID resolution and failure-path coverage. |
actions/setup/js/close_issue.cjs |
Resolves eligible canonical references and includes their IDs in intent payloads. |
Review effort: Balanced (auto)
| const closePromise = closeIssue(github, owner, repo, entityNumber, stateReason, intentMetadata, issueIntentEnabled); | ||
| const stateReasonUpper = stateReason.toUpperCase(); | ||
| const parsed = stateReasonUpper === "DUPLICATE" ? parseDuplicateOf(item.duplicate_of, owner, repo) : null; | ||
| const canonicalIssue = parsed && validateRepo(`${parsed.owner}/${parsed.repo}`, defaultTargetRepo, allowedRepos).valid && !(parsed.owner === owner && parsed.repo === repo && parsed.issueNumber === entityNumber) ? parsed : undefined; |
Comment MemoryPeek at saved memory (pr-code-quality-reviewer)Note This comment is managed by comment memory.Expand the saved memory block to view or edit the persistent context for this thread.
|
There was a problem hiding this comment.
Request changes
The new canonical-ID path still permits invalid/self duplicate references to reach the issue-intent PATCH without duplicate_issue_id, preserving the 422 failure this PR is intended to fix. The self-reference comparison also needs repository-name normalization for wildcard targets.
Details
- Ineligible or self-referential
duplicate_ofvalues becomeundefinedcanonical targets, but still emit a duplicate intent without the required ID. - Mixed-case repository references can bypass the self-reference guard because comparisons are case-sensitive.
🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt50 · ~3 AIC · ⌖ ~1 AIC · ⊞ 1.6K
Comment /review to run again
| const closePromise = closeIssue(github, owner, repo, entityNumber, stateReason, intentMetadata, issueIntentEnabled); | ||
| const stateReasonUpper = stateReason.toUpperCase(); | ||
| const parsed = stateReasonUpper === "DUPLICATE" ? parseDuplicateOf(item.duplicate_of, owner, repo) : null; | ||
| const canonicalIssue = parsed && validateRepo(`${parsed.owner}/${parsed.repo}`, defaultTargetRepo, allowedRepos).valid && !(parsed.owner === owner && parsed.repo === repo && parsed.issueNumber === entityNumber) ? parsed : undefined; |
There was a problem hiding this comment.
This still sends a duplicate close intent without the required duplicate_issue_id whenever duplicate_of is invalid, disallowed, or self-referential, so those cases continue to fail with HTTP 422 instead of closing successfully or failing before mutation.
💡 Reject or explicitly downgrade these cases
canonicalIssue becomes undefined for these inputs, but closeIssue does not know whether that means no reference was supplied or an ineligible reference was supplied. It therefore emits the same state_reason: duplicate intent payload without an ID. Please validate an ineligible/self reference before calling closeIssue (or deliberately choose the legacy close path), and add coverage for the resulting behavior rather than asserting the known-invalid PATCH shape.
| const closePromise = closeIssue(github, owner, repo, entityNumber, stateReason, intentMetadata, issueIntentEnabled); | ||
| const stateReasonUpper = stateReason.toUpperCase(); | ||
| const parsed = stateReasonUpper === "DUPLICATE" ? parseDuplicateOf(item.duplicate_of, owner, repo) : null; | ||
| const canonicalIssue = parsed && validateRepo(`${parsed.owner}/${parsed.repo}`, defaultTargetRepo, allowedRepos).valid && !(parsed.owner === owner && parsed.repo === repo && parsed.issueNumber === entityNumber) ? parsed : undefined; |
There was a problem hiding this comment.
The self-reference guard is case-sensitive even though GitHub repository names are case-insensitive, so with a wildcard target policy a reference such as Owner/Repo#100 can resolve to the issue being closed and be sent as its own duplicate_issue_id.
💡 Normalize repository names before the self check
Compare parsed.owner and parsed.repo to owner and repo after lowercasing (or use a canonical repository identity), and add a mixed-case self-reference regression test. The same guard is duplicated in the native-marking branch below, so both paths need the normalized comparison.
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /codebase-design — solid fix with good regression coverage (parameterised tests for reference formats, lookup failures, and ineligible references), but two structural concerns worth addressing before merge.
📋 Key Themes & Highlights
Key Themes
- Redundant API call:
closeIssuefetches the canonical issue for its RESTid, thenmarkIssueAsDuplicatefetches the same issue again moments later for itsnode_id. Two GET calls per duplicate-close is avoidable and adds latency/rate-limit pressure. - Duplicated eligibility logic: the "is this a valid, allowed, non-self canonical reference" check is computed once inline for
canonicalIssue(line 370) and then re-derived with separateifchecks a few lines later for the nativemarkAsDuplicatecall. Two independent implementations of the same policy risk drifting apart silently.
Positive Highlights
- ✅ Fail-closed behavior on canonical lookup failure (no legacy fallback, no partial mutation) is correctly implemented and well tested — exactly matches the PR's stated safety goal.
- ✅ Good breadth of regression tests: multiple
duplicate_ofreference formats, cross-repo resolution, 403/404/missing-ID lookup failures, and ineligible-reference cases are all covered with clear parameterised tests. - ✅ Reuses the existing
validateRepo/allowlist policy rather than introducing a parallel authorization path.
Neither issue is blocking, but consolidating the duplicate fetch and the eligibility check into shared helpers would reduce future maintenance risk. Posted as inline comments with concrete suggestions.
@copilot please address the review comments above.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · ~64 AIC · ⌖ ~16 AIC · ⊞ 10.2K
Comment /matt to run again
| owner: canonicalIssue.owner, | ||
| repo: canonicalIssue.repo, | ||
| issue_number: canonicalIssue.issueNumber, | ||
| }); |
There was a problem hiding this comment.
[/codebase-design] Fetching the canonical issue here (for its REST id) duplicates the GET that markIssueAsDuplicate makes a few lines later for the same issue's node_id, costing an extra API round trip (and an extra rate-limit hit) on every duplicate-close path.
💡 Suggested consolidation
Consider having closeIssue return (or accept a shared fetcher/cache for) the canonical issue data, or restructure so the single github.rest.issues.get call feeding duplicate_issue_id also supplies node_id to markIssueAsDuplicate, avoiding two separate fetches of the same resource per close.
@copilot please address this.
| const closePromise = closeIssue(github, owner, repo, entityNumber, stateReason, intentMetadata, issueIntentEnabled); | ||
| const stateReasonUpper = stateReason.toUpperCase(); | ||
| const parsed = stateReasonUpper === "DUPLICATE" ? parseDuplicateOf(item.duplicate_of, owner, repo) : null; | ||
| const canonicalIssue = parsed && validateRepo(`${parsed.owner}/${parsed.repo}`, defaultTargetRepo, allowedRepos).valid && !(parsed.owner === owner && parsed.repo === repo && parsed.issueNumber === entityNumber) ? parsed : undefined; |
There was a problem hiding this comment.
[/codebase-design] The eligibility check for canonicalIssue (parsed + repo-allowed + not-self-reference) is re-derived independently in the if (item.duplicate_of ...) block below (lines ~379-391). Duplicated validation logic in two places risks silent divergence if one path is updated without the other.
💡 Suggested refactor
Extract a single helper, e.g. resolveEligibleCanonicalIssue(parsed, owner, repo, entityNumber, defaultTargetRepo, allowedRepos), returning { eligible, canonicalIssue, reason }. Use its result both to decide canonicalIssue for closeIssue(...) and to drive the warnings/skip logic in the native-duplicate-marking block, so there's one source of truth for "is this a legitimate, allowed, non-self canonical reference".
@copilot please address this.

Duplicate close intents omitted the required
duplicate_issue_id, causing HTTP 422 instead of creating a pending suggestion for maintainer review.duplicate_ofto the canonical issue’s REST database ID and include it asduplicate_issue_id.