Repository navigation
Include canonical issue IDs in duplicate close intents #73078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,7 +159,7 @@ async function addIssueComment(github, owner, repo, issueNumber, message) { | |
| * @param {string} stateReason - The reason for closing: "COMPLETED", "NOT_PLANNED", or "DUPLICATE" | ||
| * @returns {Promise<{number: number, html_url: string, title: string, node_id: string}>} Issue details | ||
| */ | ||
| async function closeIssue(github, owner, repo, issueNumber, stateReason, intentMetadata, useIssueIntent) { | ||
| async function closeIssue(github, owner, repo, issueNumber, stateReason, intentMetadata, useIssueIntent, canonicalIssue) { | ||
| const baseParams = { | ||
| owner, | ||
| repo, | ||
|
|
@@ -170,6 +170,19 @@ async function closeIssue(github, owner, repo, issueNumber, stateReason, intentM | |
|
|
||
| const hasIntentMetadata = Boolean(intentMetadata && Object.keys(intentMetadata).length > 0); | ||
| if (useIssueIntent && hasIntentMetadata) { | ||
| let duplicateIssueId; | ||
| if (canonicalIssue) { | ||
| const { data } = await github.rest.issues.get({ | ||
| owner: canonicalIssue.owner, | ||
| repo: canonicalIssue.repo, | ||
| issue_number: canonicalIssue.issueNumber, | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] Fetching the canonical issue here (for its REST 💡 Suggested consolidationConsider having @copilot please address this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The canonical REST response used for |
||
| duplicateIssueId = data.id; | ||
| if (!duplicateIssueId) { | ||
| throw new Error(`${ERR_NOT_FOUND}: id missing for canonical issue ${canonicalIssue.owner}/${canonicalIssue.repo}#${canonicalIssue.issueNumber}`); | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| core.debug("Attempting issue-intent close request"); | ||
| const { data: issue } = await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", { | ||
|
|
@@ -178,6 +191,7 @@ async function closeIssue(github, owner, repo, issueNumber, stateReason, intentM | |
| issue_number: issueNumber, | ||
| state: { value: "closed", ...intentMetadata }, | ||
| state_reason: baseParams.state_reason, | ||
| ...(duplicateIssueId !== undefined ? { duplicate_issue_id: duplicateIssueId } : {}), | ||
| }); | ||
| core.debug("Issue-intent close request succeeded"); | ||
| return issue; | ||
|
|
@@ -351,15 +365,16 @@ async function main(config = {}) { | |
| const intentMetadata = issueIntentEnabled ? normalizeIssueIntentMetadata(item) : {}; | ||
| core.info(`Closing issue #${entityNumber} with state_reason=${stateReason}`); | ||
|
|
||
| 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; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the self-reference guard to compare owner and repository case-insensitively, with a mixed-case regression test in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still sends a duplicate close intent without the required 💡 Reject or explicitly downgrade these cases
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate intents now reject invalid, disallowed, and self-referential targets before any mutation; tests cover those paths. Commit
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 💡 Normalize repository names before the self checkCompare
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The shared eligibility check now rejects mixed-case self-references for both intent payloads and native duplicate marking; added regression coverage. Commit
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] The eligibility check for 💡 Suggested refactorExtract a single helper, e.g. @copilot please address this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| const closePromise = closeIssue(github, owner, repo, entityNumber, stateReason, intentMetadata, issueIntentEnabled, canonicalIssue); | ||
|
|
||
| // When duplicate_of is provided and state_reason is DUPLICATE, create the native duplicate relationship | ||
| const stateReasonUpper = stateReason.toUpperCase(); | ||
| if (item.duplicate_of !== undefined && item.duplicate_of !== null && stateReasonUpper !== "DUPLICATE") { | ||
| core.warning(`duplicate_of is set but state_reason is ${stateReason} (not DUPLICATE); native duplicate marking will be skipped`); | ||
| } | ||
| if (item.duplicate_of !== undefined && item.duplicate_of !== null && stateReasonUpper === "DUPLICATE") { | ||
| const parsed = parseDuplicateOf(item.duplicate_of, owner, repo); | ||
| if (parsed) { | ||
| // Validate canonical repo against the same allowedRepos policy | ||
| const canonicalRepoSlug = `${parsed.owner}/${parsed.repo}`; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot use withRetry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapped this canonical lookup in
withRetryusing the rate-limit retry configuration. Commit:6ba8dc4e.