diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md deleted file mode 100644 index 4a0adfcddb..0000000000 --- a/.claude/commands/dedupe.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -allowed-tools: Bash(gh:*), Bash(./scripts/comment-on-duplicates.sh:*) -description: Find duplicate GitHub issues ---- - -Find up to 3 likely duplicate issues for a given GitHub issue. - -Follow these steps precisely: - -1. Use `gh issue view ` to read the issue. If the issue is closed, or is broad product feedback without a specific bug/feature request, or already has a duplicate detection comment (containing ``), stop and report why you are not proceeding. - -2. Summarize the issue's core problem in 2-3 sentences. Identify the key terms, error messages, and affected components. - -3. Search for potential duplicates using **at least 3 different search strategies**. Run these searches in parallel. **Only consider issues with a lower issue number** (older issues) as potential originals — skip any result with a number >= the current issue. Also skip issues already labeled `duplicate`. - - `gh search issues "" --repo $GITHUB_REPOSITORY --state open -- -label:duplicate --limit 15 --json number,title | jq '[.[] | select(.number < )]'` - - `gh search issues "" --repo $GITHUB_REPOSITORY --state open -- -label:duplicate --limit 15 --json number,title | jq '[.[] | select(.number < )]'` - - `gh search issues "" --repo $GITHUB_REPOSITORY --state open -- -label:duplicate --limit 15 --json number,title | jq '[.[] | select(.number < )]'` - - `gh search issues "" --repo $GITHUB_REPOSITORY --state all -- -label:duplicate --limit 10 --json number,title | jq '[.[] | select(.number < )]'` (include closed issues for reference) - -4. For each candidate issue that looks like a potential match, read it with `gh issue view ` to verify it is truly about the same problem. Filter out false positives — issues that merely share keywords but describe different problems. - -5. If you find 1-3 genuine duplicates, post the result using the comment script: - ``` - ./scripts/comment-on-duplicates.sh --base-issue --potential-duplicates [dup2] [dup3] - ``` - -6. If no genuine duplicates are found, report that no duplicates were detected and take no further action. - -Important notes: -- Only flag issues as duplicates when you are confident they describe the **same underlying problem** -- Prefer open issues as duplicates, but closed issues can be referenced too -- Do not flag the issue as a duplicate of itself -- The base issue number is the last part of the issue reference (e.g., for `owner/repo/issues/42`, the number is `42`) diff --git a/.github/workflows/auto-close-duplicates.yml b/.github/workflows/auto-close-duplicates.yml deleted file mode 100644 index a51cb67d62..0000000000 --- a/.github/workflows/auto-close-duplicates.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Auto-close duplicate issues - -on: - schedule: - - cron: "0 9 * * *" - workflow_dispatch: - -permissions: - issues: write - -jobs: - auto-close-duplicates: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Close stale duplicate issues - uses: actions/github-script@v7 - env: - GRACE_DAYS: ${{ vars.DUPLICATE_GRACE_DAYS || '7' }} - with: - script: | - const { owner, repo } = context.repo; - const graceDays = parseInt(process.env.GRACE_DAYS, 10) || 7; - const GRACE_PERIOD_MS = graceDays * 24 * 60 * 60 * 1000; - const now = Date.now(); - - // Find all open issues with the duplicate label - const issues = await github.paginate(github.rest.issues.listForRepo, { - owner, - repo, - state: 'open', - labels: 'duplicate', - per_page: 100, - }); - - console.log(`Found ${issues.length} open issues with duplicate label`); - - let closedCount = 0; - - for (const issue of issues) { - console.log(`Processing issue #${issue.number}: ${issue.title}`); - - // Get comments to find the duplicate detection comment - const comments = await github.rest.issues.listComments({ - owner, - repo, - issue_number: issue.number, - per_page: 100, - }); - - // Find the duplicate detection comment (posted by our script) - const dupeComments = comments.data.filter(c => - c.body.includes('') - ); - - if (dupeComments.length === 0) { - console.log(` No duplicate detection comment found, skipping`); - continue; - } - - const lastDupeComment = dupeComments[dupeComments.length - 1]; - const dupeCommentAge = now - new Date(lastDupeComment.created_at).getTime(); - - if (dupeCommentAge < GRACE_PERIOD_MS) { - const daysLeft = ((GRACE_PERIOD_MS - dupeCommentAge) / (24 * 60 * 60 * 1000)).toFixed(1); - console.log(` Duplicate comment is too recent (${daysLeft} days remaining), skipping`); - continue; - } - - // Check for human comments after the duplicate detection comment - const humanCommentsAfter = comments.data.filter(c => - new Date(c.created_at) > new Date(lastDupeComment.created_at) && - c.user.type !== 'Bot' && - !c.body.includes('') && - !c.body.includes('automatically closed as a duplicate') - ); - - if (humanCommentsAfter.length > 0) { - console.log(` Has ${humanCommentsAfter.length} human comment(s) after detection, skipping`); - continue; - } - - // Check for thumbs-down reaction from the issue author - const reactions = await github.rest.reactions.listForIssueComment({ - owner, - repo, - comment_id: lastDupeComment.id, - per_page: 100, - }); - - const authorThumbsDown = reactions.data.some(r => - r.user.id === issue.user.id && r.content === '-1' - ); - - if (authorThumbsDown) { - console.log(` Issue author gave thumbs-down on duplicate comment, skipping`); - continue; - } - - // Extract the primary duplicate issue number from the comment - const dupeMatch = lastDupeComment.body.match(/#(\d+)/); - const dupeNumber = dupeMatch ? dupeMatch[1] : 'unknown'; - - // Close the issue - console.log(` Closing as duplicate of #${dupeNumber}`); - - await github.rest.issues.update({ - owner, - repo, - issue_number: issue.number, - state: 'closed', - state_reason: 'duplicate', - }); - - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: issue.number, - labels: ['autoclose'], - }); - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issue.number, - body: `This issue has been automatically closed as a duplicate of #${dupeNumber}.\n\nIf this is incorrect, please reopen this issue or create a new one.\n\nšŸ¤– Generated with [Claude Code](https://claude.ai/code)`, - }); - - closedCount++; - } - - console.log(`Done. Closed ${closedCount} duplicate issue(s).`); diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml deleted file mode 100644 index 60f7dc355d..0000000000 --- a/.github/workflows/claude-dedupe-issues.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Claude Issue Dedupe - -on: - issues: - types: [opened] - workflow_dispatch: - inputs: - issue_number: - description: 'Issue number to check for duplicates' - required: true - type: string - -permissions: - contents: read - issues: write - id-token: write - -jobs: - dedupe: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Configure AWS Credentials (OIDC) - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.BEDROCK_ACCESS_ROLE }} - aws-region: us-east-1 - - - name: Run duplicate detection - uses: anthropics/claude-code-action@v1 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - DUPLICATE_GRACE_DAYS: ${{ vars.DUPLICATE_GRACE_DAYS }} - with: - use_bedrock: "true" - github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_bots: "github-actions[bot]" - prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}" - claude_args: "--model us.anthropic.claude-sonnet-4-5-20250929-v1:0" diff --git a/.github/workflows/issue-dedupe.yml b/.github/workflows/issue-dedupe.yml new file mode 100644 index 0000000000..5bc792e2c9 --- /dev/null +++ b/.github/workflows/issue-dedupe.yml @@ -0,0 +1,43 @@ +--- +name: Issue Dedupe +on: + issues: + types: [opened] + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + inputs: + job: + description: 'Job to run' + required: true + type: choice + options: + - detect + - auto-close + default: detect + issue_number: + description: 'Issue number to check for duplicates (detect only)' + required: false + type: string + +jobs: + detect: + if: github.event_name == 'issues' || (github.event_name == 'workflow_dispatch' && inputs.job == 'detect') + uses: opensearch-project/opensearch-build/.github/workflows/issue-dedupe-detect.yml@main + permissions: + contents: read + issues: write + id-token: write + secrets: + BEDROCK_ACCESS_ROLE_ISSUE_DEDUPE: ${{ secrets.BEDROCK_ACCESS_ROLE_ISSUE_DEDUPE }} + with: + issue_number: ${{ inputs.issue_number || '' }} + grace_days: ${{ vars.DUPLICATE_GRACE_DAYS || '7' }} + + auto-close: + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.job == 'auto-close') + uses: opensearch-project/opensearch-build/.github/workflows/issue-dedupe-autoclose.yml@main + permissions: + issues: write + with: + grace_days: ${{ vars.DUPLICATE_GRACE_DAYS || '7' }} diff --git a/.github/workflows/remove-duplicate-on-activity.yml b/.github/workflows/remove-duplicate-on-activity.yml deleted file mode 100644 index 81b0ee96ad..0000000000 --- a/.github/workflows/remove-duplicate-on-activity.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Remove duplicate label on activity - -on: - issue_comment: - types: [created] - -permissions: - issues: write - -jobs: - remove-duplicate: - if: | - github.event.issue.state == 'open' && - contains(github.event.issue.labels.*.name, 'duplicate') && - github.event.comment.user.type != 'Bot' - runs-on: ubuntu-latest - steps: - - name: Remove duplicate label - uses: actions/github-script@v7 - with: - script: | - const { owner, repo } = context.repo; - const issueNumber = context.issue.number; - const commenter = context.payload.comment.user.login; - - console.log(`Removing duplicate label from issue #${issueNumber} due to comment from ${commenter}`); - - try { - await github.rest.issues.removeLabel({ - owner, - repo, - issue_number: issueNumber, - name: 'duplicate', - }); - console.log(`Successfully removed duplicate label from issue #${issueNumber}`); - } catch (error) { - if (error.status === 404) { - console.log(`duplicate label was already removed from issue #${issueNumber}`); - } else { - throw error; - } - } diff --git a/scripts/comment-on-duplicates.sh b/scripts/comment-on-duplicates.sh deleted file mode 100755 index 9c4eb49c07..0000000000 --- a/scripts/comment-on-duplicates.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -# -# Copyright OpenSearch Contributors -# SPDX-License-Identifier: Apache-2.0 -# -# Posts a formatted duplicate detection comment and adds the duplicate label. -# -# Usage: -# ./scripts/comment-on-duplicates.sh --base-issue 123 --potential-duplicates 456 789 - -set -euo pipefail - -REPO="${GITHUB_REPOSITORY:-}" -BASE_ISSUE="" -DUPLICATES=() - -while [[ $# -gt 0 ]]; do - case $1 in - --base-issue) - BASE_ISSUE="$2" - shift 2 - ;; - --potential-duplicates) - shift - while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do - DUPLICATES+=("$1") - shift - done - ;; - *) - echo "Unknown argument: $1" >&2 - exit 1 - ;; - esac -done - -if [[ -z "$BASE_ISSUE" ]]; then - echo "Error: --base-issue is required" >&2 - exit 1 -fi - -if [[ ${#DUPLICATES[@]} -eq 0 ]]; then - echo "Error: --potential-duplicates requires at least one issue number" >&2 - exit 1 -fi - -REPO_FLAG=() -if [[ -n "$REPO" ]]; then - REPO_FLAG=("--repo" "$REPO") -fi - -# Build duplicate list -DUP_LIST="" -for dup in "${DUPLICATES[@]}"; do - TITLE=$(gh issue view "$dup" "${REPO_FLAG[@]}" --json title -q .title 2>/dev/null || echo "") - if [[ -n "$TITLE" ]]; then - DUP_LIST+="- #${dup} — ${TITLE}"$'\n' - else - DUP_LIST+="- #${dup}"$'\n' - fi -done - -# Build the comment body with a hidden marker for auto-close detection -BODY=" -### Possible Duplicate - -Found **${#DUPLICATES[@]}** possible duplicate issue(s): - -${DUP_LIST} -If this is **not** a duplicate: -- Add a comment on this issue, and the \`duplicate\` label will be removed automatically, or -- šŸ‘Ž this comment to prevent auto-closure - -Otherwise, this issue will be **automatically closed in ${DUPLICATE_GRACE_DAYS:-7} days**. - -šŸ¤– Generated with [Claude Code](https://claude.ai/code)" - -# Post the comment -echo "$BODY" | gh issue comment "$BASE_ISSUE" "${REPO_FLAG[@]}" --body-file - - -# Ensure the duplicate label exists -gh label create "duplicate" \ - --description "Issue is a duplicate of an existing issue" \ - --color "cccccc" \ - "${REPO_FLAG[@]}" 2>/dev/null || true - -# Add duplicate label -gh issue edit "$BASE_ISSUE" "${REPO_FLAG[@]}" --add-label "duplicate" - -echo "Posted duplicate comment and added duplicate label to issue #${BASE_ISSUE}"