How I Bulk Closed 1000+ GitHub Issues with GitHub Actions 🚀

Kedasha - Jan 17 - - Dev Community

Now why would I want to do this huh? 🤔

Well, we have a private repo where maintainers are able to make requests to join the private maintainer community.

Someone thought it'd be cute to submit 1000+ issues to the repo with the handle @undefined and the title undefined. This was a problem because it was making it difficult to find the actual issues that were being submitted by maintainers that needed to be approved.

1600+ issues on repo

We need to do some additional work to the repo before I'm able to add validation to the issue form but in the meantime, we needed to close these issues. So, I thought a temporary solution would be to create a GitHub action that closed all of the issues - with the title containing Pending invitation request for: @undefined'. And it worked!!

We went from having over 1,600+ issues to 64 valid issues in a matter of minutes.

Here's how I did it.

Creating the GitHub Action

The first thing I did was go to my bestie chatty (GitHub Copilot Chat) and asked it if I could close 1000+ issues with a GitHub action. It gave me a few options, but I decided to go with this one:

gh copilot chat response

Then it generated the closeIssue.js file which looked like this:

gh copilot chat closeIssues.js function

Sweet, that was the bulk of the work. Now I just needed to add the action to my repo.

I created a file called close_issues.yml in the .github/workflows directory and added the following code:

name: Close Undefined Issues
on:
    workflow_dispatch:
    push:
      branches: [ "main" ]
    pull_request:
      # The branches below must be a subset of the branches above
      branches: [ "main" ]
    schedule:
      - cron: '43 0 * * 1'

jobs:
  close_issues:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 14

      - name: Install dependencies
        run: npm install

      - name: Run script
        run: node closeIssues.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Enter fullscreen mode Exit fullscreen mode

I made a few tweaks to the code, but all in all, this code was written largely in collaboration with GitHub Copilot.

The next file I created was the closeIssues.js file. I added the following code:


import { Octokit } from "@octokit/rest";

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN,
});

async function closeIssues() {
  const { data: issues } = await octokit.issues.listForRepo({
    owner: 'OWNER',
    repo: 'internal-repo',
  });

  const issuesToClose = issues.filter(issue => issue.title === 'Pending invitation request for: @undefined');

  for (const issue of issuesToClose) {
    await octokit.issues.update({
      owner: 'OWNER',
      repo: 'internal-repo',
      issue_number: issue.number,
      state: 'closed',
    });
  }
}

closeIssues().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

I added a package.json file to my repo, install the needed dependency, and then pushed the code to my repo.

Testing the Action

After about 3 (ok 4!) tries, I finally got the action to run successfully.

Initially, the action closed 30 issues at a time, because that is the limit for the GitHub API. So, I had to read the docs 🤯 to understand how to grab ALL issues that matched the criteria and closed them.

Updates

After reading the docs, I realized that I needed to use the paginate() method to get all of the issues for the specified repo. I also learned that with paginate, the issues are returned as an array, so I no longer needed const { data: issues }. I also needed to use octokit.rest.issues when making requests.

In the end, the code that ran successfully and closed all 1000+ issues AT ONCE looked like this:

import { Octokit } from "@octokit/rest";

const octokit = new Octokit({
    auth: process.env.GITHUB_TOKEN,
});

async function closeIssues() {
    try {
        console.log('Fetching issues...');
        const issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
            owner: 'OWNER',
            repo: 'internal-repo',
            state: 'open',
        });

        console.log(`Fetched ${issues.length} issues.`);
        const issuesToClose = issues.filter(issue => issue.title === 'Pending invitation request for: @undefined');
        console.log(`Found ${issuesToClose.length} issues to close.`);

        for (const issue of issuesToClose) {
            console.log(`Closing issue #${issue.number}...`);
            await octokit.rest.issues.update({
                owner: 'OWNER',
                repo: 'internal-repo',
                issue_number: issue.number,
                state: 'closed',
            });
            console.log(`Closed issue #${issue.number}.`);
        }
    } catch (error) {
        console.error(error);
    }
}

closeIssues();

Enter fullscreen mode Exit fullscreen mode

I added some logging to the code so I could see what was happening during the workflow run. I also added a try/catch block to catch any errors that may occur.

action_autoclose

Conclusion

I was very happy that I could do this because it would've taken my teammate HOURS to close all these issues manually. I was able to do it in a matter of minutes! I'm super stoked that GitHub Copilot was able to teach me something new - how to interact with the GitHub API using the Octokit library. 🚀

64 open issues on repo

Have you ever used GitHub Copilot to help you write GitHub Actions? Let me know in the comments below! I hope you learned something new today.

Until next time, happy coding! 😁

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player