Skip to content

Conversation

@rmallorybpc
Copy link
Collaborator

This PR is to add the automated process to monitor, report on, and reply to the Issues in this repo.

This workflow automates the weekly review of open issues, categorizing them and posting a summary comment on a specified issue.
This workflow automatically creates a monthly GitHub triage issue and closes the previous month's issue. It includes steps to compute the current and previous month, check for existing issues, and create a new issue if necessary.
This composite action creates a monthly triage issue and can close the previous month's issue if specified. It includes inputs for closing the previous issue, applying a label, and using a custom body template.
This workflow automates monthly triage of issues using a composite action, scheduled to run on the first day of each month.
This document provides a comprehensive guide for implementing an automated monthly triage process on GitHub, including workflows for issue management and comments.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an automated triage system for monitoring, reporting, and managing issues in the repository through GitHub Actions workflows. The system creates monthly triage tracking issues and posts weekly summary comments categorizing new issues.

  • Adds three GitHub Actions workflows: monthly triage issue creation, weekly comment updates, and a composite action-based alternative
  • Implements a reusable composite action to encapsulate triage issue management logic
  • Provides comprehensive documentation for implementation and usage

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
.github/workflows/weekly-content-review.yml Weekly workflow that posts categorized summaries of new issues to the current month's triage tracking issue
.github/workflows/use-composite-triage.yml Alternative monthly workflow demonstrating usage of the reusable composite action
.github/workflows/auto-triage-issue.yml Primary monthly workflow that creates current month's triage issue and closes the previous month's issue
.github/actions/triage-issues/action.yml Reusable composite action encapsulating triage issue creation and previous issue closing logic
.github/monthly-instructions.md Step-by-step implementation guide and documentation for the triage automation system

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

jobs:
triage:
runs-on: ubuntu-latest
steps:
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow references a local composite action at ./.github/actions/triage-issue, but there's no actions/checkout@v4 step before it. Without checking out the repository, the local action files won't be available, causing the workflow to fail. Add a checkout step before line 15.

Suggested change
steps:
steps:
- name: Check out repository
uses: actions/checkout@v4

Copilot uses AI. Check for mistakes.

4. Add the reusable composite action
- Encapsulates logic to:
- Optionally close last month’s triage issue
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "Optionaly" should be "Optionally".

Copilot uses AI. Check for mistakes.

on:
schedule:
- cron: '27 13 * * 1' # 13:27 UTC every Monday (8:27 AM EST in November)
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment in the cron schedule says "8:27 AM EST in November", but EST (Eastern Standard Time) is only applicable during winter months when daylight saving is not in effect. This comment will be misleading during daylight saving time (roughly March-November) when the time zone is actually EDT (Eastern Daylight Time, UTC-4 instead of UTC-5). Consider updating the comment to say "8:27 AM ET" (Eastern Time) or explicitly mention the daylight saving variation.

Suggested change
- cron: '27 13 * * 1' # 13:27 UTC every Monday (8:27 AM EST in November)
- cron: '27 13 * * 1' # 13:27 UTC every Monday (8:27 AM ET)

Copilot uses AI. Check for mistakes.
runs-on: ubuntu-latest
steps:
- name: Run composite triage action
uses: ./.github/actions/triage-issue
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composite action path referenced here (triage-issue) doesn't match the actual directory name (triage-issues with an 's'). This will cause the workflow to fail when trying to locate the action. The path should be ./.github/actions/triage-issues.

Suggested change
uses: ./.github/actions/triage-issue
uses: ./.github/actions/triage-issues

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +101
- name: Ensure triage issue
uses: actions/github-script@v7
with:
script: |
const closePrev = (process.env.INPUT_CLOSE_PREVIOUS || 'true').toLowerCase() === 'true';
const label = process.env.INPUT_LABEL || 'triage';
const bodyTemplate = process.env.INPUT_BODY_TEMPLATE;
const curr = '${{ steps.dates.outputs.curr }}';
const prev = '${{ steps.dates.outputs.prev }}';
const currTitle = `GitHub Triage: ${curr}`;
const prevTitle = `GitHub Triage: ${prev}`;

async function findIssue(title) {
const perPage = 100;
for (let page = 1; page < 50; page++) {
const { data } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: perPage,
page
});
if (!data.length) break;
const hit = data.find(i => i.title === title);
if (hit) return hit;
if (data.length < perPage) break;
}
return null;
}

if (closePrev) {
const prevIssue = await findIssue(prevTitle);
if (prevIssue && prevIssue.state === 'open') {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prevIssue.number,
state: 'closed'
});
core.info(`Closed previous triage issue #${prevIssue.number} (${prevTitle})`);
} else {
core.info(`Previous triage issue not open or not found (${prevTitle}).`);
}
}

const currIssue = await findIssue(currTitle);
if (currIssue) {
core.info(`Current triage issue already exists: #${currIssue.number}`);
return;
}

const body = bodyTemplate.replace(/{{MONTH}}/g, curr);
const created = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: currTitle,
body,
labels: [label]
});
core.notice(`Created triage issue #${created.data.number} (${currTitle}).`);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composite action reads inputs via environment variables (e.g., process.env.INPUT_CLOSE_PREVIOUS, process.env.INPUT_LABEL, process.env.INPUT_BODY_TEMPLATE), but these environment variables are never set. In GitHub Actions, inputs need to be explicitly passed as environment variables using the env key in the step. Without this, the script will use default values or undefined behavior.

Copilot uses AI. Check for mistakes.
4. Add the reusable composite action
- Encapsulates logic to:
- Optionally close last month’s triage issue
-Create the current month’s triage issue
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after hyphen. Should be "- Create the current month's triage issue" (with a space after the hyphen).

Suggested change
-Create the current month’s triage issue
- Create the current month’s triage issue

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +49
async function findIssueByExactTitle(title) {
const perPage = 100;
for (let page = 1; page < 50; page++) {
const { data } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: perPage,
page
});
if (!data.length) break;
const hit = data.find(i => i.title === title);
if (hit) return hit;
if (data.length < perPage) break;
}
return null;
}
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The findIssueByExactTitle function logic is duplicated across multiple files (here and in weekly-content-review.yml line 32-48, and triage-issues/action.yml line 54-70). This duplication makes maintenance harder as any bug fixes or improvements need to be applied in multiple places. Consider consolidating this logic into a shared GitHub Action or a reusable JavaScript module.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +48
const perPage = 100;
for (let page = 1; page < 50; page++) {
const { data } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: perPage,
page
});
if (!data.length) break;
const hit = data.find(i => i.title === title);
if (hit) return hit;
if (data.length < perPage) break;
}
return null;
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pagination logic iterates through up to 5000 issues (50 pages × 100 per page) to find a single issue by exact title match. This is inefficient and will result in many unnecessary API calls. Consider using the GitHub Search API with a query like repo:owner/repo is:issue "${title}" to find the issue directly, which would be a single API call instead of potentially many.

Suggested change
const perPage = 100;
for (let page = 1; page < 50; page++) {
const { data } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: perPage,
page
});
if (!data.length) break;
const hit = data.find(i => i.title === title);
if (hit) return hit;
if (data.length < perPage) break;
}
return null;
// Use GitHub Search API for efficiency
const query = [
`repo:${context.repo.owner}/${context.repo.repo}`,
'is:issue',
'in:title',
`"${title.replace(/"/g, '\\"')}"`
].join(' ');
const { data } = await github.rest.search.issuesAndPullRequests({
q: query,
per_page: 10,
});
const hit = data.items.find(i => i.title === title);
return hit || null;

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,101 @@
name: "triage-issue"
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composite action is named "triage-issue" (singular) in the action metadata, but the directory name is "triage-issues" (plural). This naming inconsistency can cause confusion. Consider making them consistent - either both singular or both plural.

Suggested change
name: "triage-issue"
name: "triage-issues"

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +106
if (!reTemplate.test(title) && reSuggested.test(title)) return 'suggested';
if (reOther.test(title)) return 'other';
if (/update|request/i.test(title) && !reTemplate.test(title)) return 'suggested';
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The categorization logic checks for reTemplate.test(title) three times (lines 101, 104, 106), which is redundant and impacts performance. The first check on line 101 already returns 'template' if the condition is met, so subsequent checks are unnecessary. Consider removing the redundant checks on lines 104 and 106.

Suggested change
if (!reTemplate.test(title) && reSuggested.test(title)) return 'suggested';
if (reOther.test(title)) return 'other';
if (/update|request/i.test(title) && !reTemplate.test(title)) return 'suggested';
if (reSuggested.test(title)) return 'suggested';
if (reOther.test(title)) return 'other';
if (/update|request/i.test(title)) return 'suggested';

Copilot uses AI. Check for mistakes.
Copy link

@chriswblake chriswblake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest changing the PR title to reflect what the PR actually changes. Even better, put a few bullet points in the description to clarify the new features.

The hardest part of a review is typically trying to understand the context of what the other person built. Adding that helps a lot to understand what you are looking at.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically docs are not put inside the .github folder.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Having read this more, I think it was just your prompt for Copilot to build the scripts. If that is the case, I would not commit this. I totally thought I should read this first, thinking it was the documentation for the above workflow files.

@@ -0,0 +1,54 @@
# Below is a consolidated, step-by-step guide and the complete, updated code to implement an automated process that:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to an actual title, so I know what it does. Right now, this is super generic title.

uses: actions/github-script@v7
with:
script: |
const ym = '${{ steps.dates.outputs.year_month }}';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not pass variables directly into the script. Variables passed in this way are a direct text replacement before running the script. As such, they can easily break things and make it hard to debug.

Instead pass variables like this:

with:
  script: |
   ...
env:
  YEAR_MONTH: ${{ steps.dates.outputs.year_month }}


async function findIssue(title) {
const perPage = 100;
for (let page = 1; page < 50; page++) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it is simply getting all open issues. I would instead include filters for date range, label, and title content (depending on how you are organizing).

Similar to how you are using the "state" to filter if it is open. Here are the filter options.
https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues

page
});
if (!data.length) break;
const hit = data.find(i => i.title === title);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could for example move this "if" directly into the API call.

@@ -0,0 +1,29 @@
name: Monthly Triage via Composite Action

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend keeping the workflow name and file name the same. This makes it easier to identify from the Actions tab to the actual file.

with:
close-previous: "true"
label: "triage"
body-template: |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good use of putting the template in the ENV area. Makes it much easier to read and update without worry of breaking the script.

@@ -0,0 +1,97 @@
name: Auto Create Monthly GitHub Triage Issue (Create + Close Previous)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misinterpreted the name of this file and the title.
I was a bit confused how you can triage "1" issue. Coming in I figured you meant to make it plural.
Now, I get that it is creating a new issue.

uses: actions/github-script@v7
with:
script: |
const current = core.getInput('current') || '${{ steps.dates.outputs.year_month }}';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused. This workflow has no inputs. Where is "current" coming from?
Does this actually run?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, generic variables again. Current issue?, Current Date?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could integrate this directly into weekly-content-review.yml. That workflow already a check that throws an error if the issue does exist. So, instead of throwing an error, it would just trigger the issue to be created first.

In that case, I think you could also get rid of triage-issues/action.yml and use-composite-triage.yml‎. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants