-
Notifications
You must be signed in to change notification settings - Fork 8
Update monthly review #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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.
There was a problem hiding this 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: |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| steps: | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 |
|
|
||
| 4. Add the reusable composite action | ||
| - Encapsulates logic to: | ||
| - Optionally close last month’s triage issue |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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".
|
|
||
| on: | ||
| schedule: | ||
| - cron: '27 13 * * 1' # 13:27 UTC every Monday (8:27 AM EST in November) |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| - 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) |
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Run composite triage action | ||
| uses: ./.github/actions/triage-issue |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| uses: ./.github/actions/triage-issue | |
| uses: ./.github/actions/triage-issues |
| - 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}).`); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| 4. Add the reusable composite action | ||
| - Encapsulates logic to: | ||
| - Optionally close last month’s triage issue | ||
| -Create the current month’s triage issue |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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).
| -Create the current month’s triage issue | |
| - Create the current month’s triage issue |
| 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; | ||
| } |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| 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; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| 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; |
| @@ -0,0 +1,101 @@ | |||
| name: "triage-issue" | |||
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| name: "triage-issue" | |
| name: "triage-issues" |
| 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'; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
| 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'; |
chriswblake
left a comment
There was a problem hiding this 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: | |||
There was a problem hiding this comment.
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 }}'; |
There was a problem hiding this comment.
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++) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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: | |
There was a problem hiding this comment.
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) | |||
There was a problem hiding this comment.
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 }}'; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. :)
This PR is to add the automated process to monitor, report on, and reply to the Issues in this repo.