surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| 1 | name: Auto-close duplicate issues (DRY RUN) |
| 2 | description: Dry run - logs issues that would be auto-closed as duplicates after 3 days if no response |
| 3 | on: |
| 4 | schedule: |
| 5 | - cron: '0 9 * * *' |
| 6 | |
| 7 | jobs: |
| 8 | auto-close-duplicates: |
| 9 | runs-on: ubuntu-latest |
| 10 | timeout-minutes: 10 |
| 11 | permissions: |
| 12 | contents: read |
| 13 | issues: write |
| 14 | |
| 15 | steps: |
| 16 | - name: Auto-close duplicate issues |
| 17 | uses: actions/github-script@v7 |
| 18 | with: |
| 19 | script: | |
| 20 | const threeDaysAgo = new Date(); |
| 21 | threeDaysAgo.setDate(threeDaysAgo.getDate() - 3); |
| 22 | |
| 23 | // Get all open issues |
| 24 | const { data: issues } = await github.rest.issues.listForRepo({ |
| 25 | owner: context.repo.owner, |
| 26 | repo: context.repo.repo, |
| 27 | state: 'open', |
| 28 | per_page: 100 |
| 29 | }); |
| 30 | |
| 31 | for (const issue of issues) { |
| 32 | // Get comments for this issue |
| 33 | const { data: comments } = await github.rest.issues.listComments({ |
| 34 | owner: context.repo.owner, |
| 35 | repo: context.repo.repo, |
| 36 | issue_number: issue.number |
| 37 | }); |
| 38 | |
| 39 | // Find duplicate comments (look for "Found 1 possible duplicate" or "Found X possible duplicate") |
| 40 | const dupeComments = comments.filter(comment => |
| 41 | comment.body.includes('Found') && |
| 42 | comment.body.includes('possible duplicate') && |
| 43 | comment.user.type === 'Bot' |
| 44 | ); |
| 45 | |
| 46 | if (dupeComments.length === 0) continue; |
| 47 | |
| 48 | // Get the most recent duplicate comment |
| 49 | const lastDupeComment = dupeComments[dupeComments.length - 1]; |
| 50 | const dupeCommentDate = new Date(lastDupeComment.created_at); |
| 51 | |
| 52 | // Check if the duplicate comment is 3+ days old |
| 53 | if (dupeCommentDate > threeDaysAgo) continue; |
| 54 | |
| 55 | // Check if there are any comments after the duplicate comment |
| 56 | const commentsAfterDupe = comments.filter(comment => |
| 57 | new Date(comment.created_at) > dupeCommentDate |
| 58 | ); |
| 59 | |
| 60 | if (commentsAfterDupe.length > 0) continue; |
| 61 | |
| 62 | // Check if issue author reacted with thumbs down to the duplicate comment |
| 63 | const { data: reactions } = await github.rest.reactions.listForIssueComment({ |
| 64 | owner: context.repo.owner, |
| 65 | repo: context.repo.repo, |
| 66 | comment_id: lastDupeComment.id |
| 67 | }); |
| 68 | |
| 69 | const authorThumbsDown = reactions.some(reaction => |
| 70 | reaction.user.id === issue.user.id && reaction.content === '-1' |
| 71 | ); |
| 72 | |
| 73 | if (authorThumbsDown) continue; |
| 74 | |
| 75 | // DRY RUN: Log the issue that would be auto-closed |
| 76 | const issueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${issue.number}`; |
| 77 | console.log(`[DRY RUN] Would auto-close issue #${issue.number} as duplicate: ${issueUrl}`); |
| 78 | } |