Skip to content
Latchkey

Close Inactive Issues workflow (nunchaku-ai/ComfyUI-nunchaku)

The Close Inactive Issues workflow from nunchaku-ai/ComfyUI-nunchaku, explained and optimized by Latchkey.

A

CI health: A - excellent

Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: nunchaku-ai/ComfyUI-nunchaku.github/workflows/close-inactive-issues.yamlLicense Apache-2.0View source

What it does

This is the Close Inactive Issues workflow from the nunchaku-ai/ComfyUI-nunchaku repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
# Borrowed from https://github.com/sgl-project/sglang/blob/main/.github/workflows/close-inactive-issues.yml
name: Close Inactive Issues
on:
  schedule:
    - cron: '0 0 * * *'
permissions:
  issues: write
  contents: read
jobs:
  close-inactive-issues:
    if: github.repository == 'nunchaku-ai/ComfyUI-nunchaku'
    runs-on: ubuntu-latest
    steps:
      - name: Check and close inactive issues
        uses: actions/github-script@v6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

            const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
            console.log(`Owner: ${owner}, Repo: ${repo}`);

            async function fetchIssues(page = 1) {
              console.log(`Fetching issues for ${owner}/${repo}, page ${page}`);
              return await github.rest.issues.listForRepo({
                owner,
                repo,
                state: 'open',
                sort: 'updated',
                direction: 'asc',
                per_page: 100,
                page: page
              });
            }

            async function processIssues() {
              console.log('Starting to process issues');
              console.log(`Repository: ${owner}/${repo}`);

              let page = 1;
              let hasMoreIssues = true;
              while (hasMoreIssues) {
                try {
                  const issues = await fetchIssues(page);
                  console.log(`Fetched ${issues.data.length} issues on page ${page}`);

                  if (issues.data.length === 0) {
                    hasMoreIssues = false;
                    break;
                  }

                  for (const issue of issues.data) {
                    // Skip if the issue has 'good first issue' label
                    if (issue.labels.some(label => label.name === 'good first issue')) {
                      console.log(`Skipping issue #${issue.number} as it's marked as 'good first issue'`);
                      continue;
                    }
                    if (new Date(issue.updated_at) < thirtyDaysAgo) {
                      try {
                        await github.rest.issues.update({
                          owner,
                          repo,
                          issue_number: issue.number,
                          state: 'closed',
                          labels: [...issue.labels.map(l => l.name), 'inactive']
                        });
                        await github.rest.issues.createComment({
                          owner,
                          repo,
                          issue_number: issue.number,
                          body: 'This issue has been automatically closed due to 30-day inactivity. Please feel free to reopen it with \`/reopen\` if needed.'
                        });
                        console.log(`Closed issue #${issue.number} due to inactivity.`);
                      } catch (error) {
                        console.error(`Failed to close issue #${issue.number}: ${error.message}`);
                      }
                    } else {
                      console.log(`Issue #${issue.number} is still active. Stopping processing.`);
                      hasMoreIssues = false;
                      break;
                    }
                  }
                  page += 1;
                } catch (error) {
                  console.error(`Error fetching issues on page ${page}: ${error.message}`);
                  hasMoreIssues = false;
                }
              }
              console.log('Finished processing issues');
            }

            await processIssues();

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

# Borrowed from https://github.com/sgl-project/sglang/blob/main/.github/workflows/close-inactive-issues.yml
name: Close Inactive Issues
on:
  schedule:
    - cron: '0 0 * * *'
permissions:
  issues: write
  contents: read
jobs:
  close-inactive-issues:
    timeout-minutes: 30
    if: github.repository == 'nunchaku-ai/ComfyUI-nunchaku'
    runs-on: latchkey-small
    steps:
      - name: Check and close inactive issues
        uses: actions/github-script@v6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
 
            const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
            console.log(`Owner: ${owner}, Repo: ${repo}`);
 
            async function fetchIssues(page = 1) {
              console.log(`Fetching issues for ${owner}/${repo}, page ${page}`);
              return await github.rest.issues.listForRepo({
                owner,
                repo,
                state: 'open',
                sort: 'updated',
                direction: 'asc',
                per_page: 100,
                page: page
              });
            }
 
            async function processIssues() {
              console.log('Starting to process issues');
              console.log(`Repository: ${owner}/${repo}`);
 
              let page = 1;
              let hasMoreIssues = true;
              while (hasMoreIssues) {
                try {
                  const issues = await fetchIssues(page);
                  console.log(`Fetched ${issues.data.length} issues on page ${page}`);
 
                  if (issues.data.length === 0) {
                    hasMoreIssues = false;
                    break;
                  }
 
                  for (const issue of issues.data) {
                    // Skip if the issue has 'good first issue' label
                    if (issue.labels.some(label => label.name === 'good first issue')) {
                      console.log(`Skipping issue #${issue.number} as it's marked as 'good first issue'`);
                      continue;
                    }
                    if (new Date(issue.updated_at) < thirtyDaysAgo) {
                      try {
                        await github.rest.issues.update({
                          owner,
                          repo,
                          issue_number: issue.number,
                          state: 'closed',
                          labels: [...issue.labels.map(l => l.name), 'inactive']
                        });
                        await github.rest.issues.createComment({
                          owner,
                          repo,
                          issue_number: issue.number,
                          body: 'This issue has been automatically closed due to 30-day inactivity. Please feel free to reopen it with \`/reopen\` if needed.'
                        });
                        console.log(`Closed issue #${issue.number} due to inactivity.`);
                      } catch (error) {
                        console.error(`Failed to close issue #${issue.number}: ${error.message}`);
                      }
                    } else {
                      console.log(`Issue #${issue.number} is still active. Stopping processing.`);
                      hasMoreIssues = false;
                      break;
                    }
                  }
                  page += 1;
                } catch (error) {
                  console.error(`Error fetching issues on page ${page}: ${error.message}`);
                  hasMoreIssues = false;
                }
              }
              console.log('Finished processing issues');
            }
 
            await processIssues();
 

What changed

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow