DEV Community

Cover image for How I Bulk Closed 1000+ GitHub Issues with GitHub Actions πŸš€
Kedasha for GitHub

Posted on

How I Bulk Closed 1000+ GitHub Issues with GitHub Actions πŸš€

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! 😁

Top comments (14)

Collapse
 
cassidoo profile image
Cassidy Williams

This is so helpful, thank you for writing it!

Collapse
 
ladykerr profile image
Kedasha

awh! Thanks Cassidy!

Collapse
 
michaeltharrington profile image
Michael Tharrington

As someone who has spent a lot of time removing junk content from this very community, I very much feel you on this. And wow, I appreciate you keeping your teammates from having to close each one of these out manually... that would've been such a headache. πŸ™€

Collapse
 
ladykerr profile image
Kedasha

such a headache! They were trucking through them because no time to look into it previously. Such a weight lifted tbh

Collapse
 
luc122c profile image
Luke Nelson

Good solution! When I had to batch- update pull requests from β€œReady” to β€œDraft”, I used a bash script and the GitHub CLI. It took a while to run, I suspect the CLI was trying to avoid rate limits, but it worked. The end result was very similar to your action, except I could run it locally :)

Collapse
 
ladykerr profile image
Kedasha

oh nice!

Collapse
 
ben profile image
Ben Halpern

πŸ˜΅β€πŸ’«

Collapse
 
ladykerr profile image
Kedasha

right, lol

Collapse
 
pratikaambani profile image
Pratik Ambani

Great one! Can you suggest decent tutorials to learn GitHub Actions?

Collapse
 
ladykerr profile image
Kedasha

yes! You can learn GitHub Actions here: learn.microsoft.com/en-us/collecti... THEN get certified in GitHub Actions: resources.github.com/learn/certifi... after you complete the training!

Collapse
 
jamesmurdza profile image
James Murdza

What's the source/reason for this kind of spam?

Collapse
 
ladykerr profile image
Kedasha

That's what Im saying!

Collapse
 
der_gopher profile image
Alex Pliutau

Great write-up! We also have a bunch of articles on Github Actions in our Newsletter, check it out - packagemain.tech/p/github-actions-...

Collapse
 
nicekate profile image
nicekate

Awesome, thanks for sharing

Some comments may only be visible to logged-in visitors. Sign in to view all comments.