Skip to content

Conversation

@rmallorybpc
Copy link
Collaborator

This pull request introduces a fully JavaScript-driven automation to monitor, reply to, and report GitHub issues on a monthly and weekly cadence. The solution creates a “GitHub Triage: YYYY-MM” issue each month (and closes the previous month’s triage issue if open), and posts weekly summary comments listing and categorizing issues created within the last 7 days. Logic is centralized in Node.js scripts for maintainability, extensibility, and future reuse.

What’s included

Node.js scripts (Octokit-based)

ensureMonthlyTriage.js
Closes last month’s triage issue if open.
Ensures current month triage issue exists with title “GitHub Triage: YYYY-MM”.
Creates the issue (with triage label) and a structured body if missing.
weeklyTriageComment.js
Finds the current month triage issue.
Searches for issues created in the last 7 days using the GitHub Search API.
Categorizes issues via regex buckets (grammar, deprecated/outdated, suggested, other, template-incomplete).
Posts a weekly comment including counts and each issue reference on its own line (“- #123”), enabling GitHub to dynamically render titles and status.
Workflows

.github/workflows/auto-triage-issue.yml
Runs monthly at 07:05 UTC on the 1st day of the month.
Uses Node 18 and npm ci, then runs ensureMonthlyTriage.js.
.github/workflows/weekly-triage-comment.yml
Runs weekly at 13:27 UTC every Monday (8:27 AM EST in November).
Uses Node 18 and npm ci, then runs weeklyTriageComment.js.
Optional composite action + workflow
.github/actions/triage-issue/action.yml: Minimal composite action to run ensureMonthlyTriage.js (for reuse).
.github/workflows/use-composite-triage.yml: Demonstrates invoking the composite action monthly.
Package management

package.json: Declares @octokit/core dependency, Node engines, and npm scripts.
package-lock.json: Ensures reproducible installs via npm ci.
Implementation details

Octokit REST API with pagination safeguards.
Weekly search query: repo:
o
w
n
e
r
/
{repo} is:issue is:open created:>=YYYY-MM-DD.
Weekly comment template includes:
Per-category counts (grammar, deprecated/outdated, suggested, other, template).
Each issue listed on its own line for dynamic title/status rendering.
Audit note that Mona (Copilot) reviewed the update.
Permissions scoped to issues: write and contents: read.
Node version pinned to 18; npm ci used for deterministic installs.
Configuration and environment

Required env vars in workflow steps:
GITHUB_TOKEN: Provided automatically by GitHub Actions (secrets.GITHUB_TOKEN).
REPO_OWNER: githubpartners (or override).
REPO_NAME: microsoft-learn (or override).
Timezones can be adjusted by editing the cron expressions in workflows.
Testing and validation

Manual run: Trigger both workflows via workflow_dispatch to validate issue creation/closing and comment posting.
Check the created triage issue’s body and format.
Confirm weekly comment lists each issue with “- #” and shows counts per category.
Validate behavior when no issues are found (comment is not posted or posts “no new issues” message if you configure that path).
Backward compatibility and risk

Additive change: New scripts, workflows, and composite action.
Low risk: Permissions constrained to issues and read-only contents.
Rate limits: Pagination limits implemented; consider backoff or GraphQL in future if volume increases.
Future enhancements

package.json scripts for local dry runs and testing (e.g., “triage:dry”, “weekly:dry”).
GraphQL queries for efficient exact title lookup and reduced payload.
NLP-based classification to improve beyond regex buckets.
Slack/Teams notifications for creation and weekly updates.
JSON artifacts and dashboards (e.g., commit weekly snapshots to reports/monthly/).
Idempotent weekly comments (detect/update if one already exists for the same week).
TypeScript + lint/test CI with caching for node_modules.
Files added

.github/scripts/ensureMonthlyTriage.js
.github/scripts/weeklyTriageComment.js
.github/workflows/auto-triage-issue.yml
.github/workflows/weekly-triage-comment.yml
.github/actions/triage-issue/action.yml (optional)
.github/workflows/use-composite-triage.yml (optional)
package.json
package-lock.json
Checklist

Workflows appear in Actions tab
Monthly triage issue created/closed correctly
Weekly comment posts with categorized counts and per-issue lines
Permissions (issues: write, contents: read) are sufficient
Node setup and npm ci complete in CI

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 pull request aims to introduce automated GitHub issue triage using JavaScript-driven workflows that run monthly and weekly. However, the PR contains critical structural issues that will prevent it from functioning correctly. The implementation includes multiple file naming inconsistencies, path mismatches between workflows and scripts, and duplicated "version" files that suggest incomplete cleanup from development iterations.

Key Issues:

  • JavaScript scripts are named with underscores (e.g., github_scripts_*.js) but referenced as if they're in a scripts/ subdirectory
  • package.json is misnamed as package_Version5.json and located in .github/ instead of the repository root
  • Multiple duplicate files with version suffixes (Version5, Version6, Version7) that conflict with actual workflow files
  • Action path mismatch: workflows reference triage-issue (singular) but the action is at triage-issues (plural)
  • Missing checkout steps in composite action workflows

Summary:

  • Monthly workflow to create "GitHub Triage: YYYY-MM" issues and close previous month's triage issue
  • Weekly workflow to post comments categorizing issues opened in the last 7 days
  • Composite action for reusable triage issue creation
  • Node.js scripts using Octokit for GitHub API interactions

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 24 comments.

Show a summary per file
File Description
.github/workflows/weekly-content-review.yml Weekly workflow to search and categorize new issues, post comments to monthly triage issue (misnamed - should be weekly-triage-comment.yml per description)
.github/workflows/use-composite-triage.yml Workflow to invoke composite action for monthly triage (missing checkout, incorrect action path)
.github/workflows/auto-triage-issue.yml Monthly workflow using github-script to create/close triage issues inline
.github/package_Version5.json npm package definition (incorrect filename and location, should be package.json at repo root)
.github/monthly-instructions.md Implementation guide and documentation
.github/github_workflows_weekly-triage-comment_Version6.yml Versioned duplicate of weekly workflow using Node.js script approach (should be removed or consolidated)
.github/github_workflows_use-composite-triage_Version5.yml Versioned duplicate of composite action workflow (should be removed or consolidated)
.github/github_workflows_auto-triage-issue_Version7.yml Versioned duplicate of monthly triage workflow using Node.js script (should be removed or consolidated)
.github/github_scripts_weeklyTriageComment.js JavaScript to post weekly triage comments (incorrect filename/path - workflows expect .github/scripts/weeklyTriageComment.js)
.github/github_scripts_ensureMonthlyTriage.js JavaScript to create/close monthly triage issues (incorrect filename/path - workflows expect .github/scripts/ensureMonthlyTriage.js)
.github/github_actions_triage-issue_action_Version6.yml Versioned composite action that runs Node.js scripts (incorrect filename, missing env vars, should be in actions/ subdirectory)
.github/actions/triage-issues/action.yml Composite action using github-script to create monthly triage issues (correct structure but path mismatch with workflow reference)

💡 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 11, 2025

Choose a reason for hiding this comment

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

The workflow is missing a checkout step before attempting to use the local composite action. Composite actions referenced with relative paths (starting with ./) require the repository to be checked out first. Add a checkout step using actions/checkout@v4 before this step.

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

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +29
const current = core.getInput('current') || '${{ steps.dates.outputs.year_month }}';
const previous = core.getInput('previous') || '${{ steps.dates.outputs.prev_year_month }}';
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The use of core.getInput() is incorrect in this context. The getInput() function is meant for reading inputs in GitHub Actions (custom actions or composite actions), not for workflows using github-script. Since this workflow doesn't define any inputs, these calls will always return empty strings, causing the fallback to the step outputs to be used. The core.getInput('current') and core.getInput('previous') calls should be removed, and the code should directly use the step output values.

Suggested change
const current = core.getInput('current') || '${{ steps.dates.outputs.year_month }}';
const previous = core.getInput('previous') || '${{ steps.dates.outputs.prev_year_month }}';
const current = '${{ steps.dates.outputs.year_month }}';
const previous = '${{ steps.dates.outputs.prev_year_month }}';

Copilot uses AI. Check for mistakes.
- cron: '2 7 1 * *' # 07:02 UTC on 1st day of month
workflow_dispatch:

permissions:
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The workflow is missing the contents: read permission. While issues: write is correctly specified, the composite action performs a checkout (if it's the proper version), and GitHub Actions best practice recommends explicitly declaring contents: read when checking out code. This is especially important for security and clarity about what permissions the workflow requires.

Suggested change
permissions:
permissions:
contents: read

Copilot uses AI. Check for mistakes.
- cron: '2 7 1 * *' # 07:02 UTC on 1st day of month
workflow_dispatch:

permissions:
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The workflow is missing the contents: read permission. While issues: write is correctly specified, the workflow attempts to use a local composite action with a relative path (./.github/actions/triage-issue), which requires checking out the repository. GitHub Actions best practice recommends explicitly declaring contents: read when checking out code for security and clarity.

Suggested change
permissions:
permissions:
contents: read

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +65
for (let page = 1; page < 10; page++) {
const resp = await octokit.request("GET /search/issues", {
q,
per_page: 100,
page,
});
const items = resp.data.items || [];
allItems = allItems.concat(items);
if (items.length < 100) break;
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The pagination loop doesn't implement any rate limit handling or backoff strategy. While the GitHub API includes rate limit headers, this code doesn't check for rate limit errors (status 403 with specific error messages) or implement exponential backoff. For workflows that run frequently or repositories with many issues, consider adding error handling for rate limit exceptions (e.g., checking resp.status and X-RateLimit-Remaining header) to prevent workflow failures.

Copilot uses AI. Check for mistakes.
page,
});
const items = resp.data.items || [];
allItems = allItems.concat(items);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The GitHub Search API can return both issues and pull requests. While the query includes is:issue which should filter to only issues, it's safer to add an explicit filter to exclude pull requests. Consider filtering the items: allItems = allItems.concat(items.filter(item => !item.pull_request)) to ensure pull requests are never included in the triage comment, especially since the workflow is specifically for issue triage.

Suggested change
allItems = allItems.concat(items);
allItems = allItems.concat(items.filter(item => !item.pull_request));

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +115
await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
{
owner: repoOwner,
repo: repoName,
issue_number: triageIssue.number,
body: md,
}
);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The script posts a comment even when there are no new issues in the last 7 days. When allItems.length is 0, the comment will show all category counts as 0 and display "Total new issues: 0" with an empty "Issue References" section. Consider adding a check to skip posting the comment (or post a different message) when no new issues are found, to avoid cluttering the triage issue with empty weekly updates.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +99
// ensureMonthlyTriage.js
const { Octokit } = require("@octokit/core");

const githubToken = process.env.GITHUB_TOKEN;
const repoOwner = process.env.REPO_OWNER;
const repoName = process.env.REPO_NAME;

if (!githubToken || !repoOwner || !repoName) {
console.error("Missing required env: GITHUB_TOKEN, REPO_OWNER, REPO_NAME");
process.exit(1);
}

const octokit = new Octokit({ auth: githubToken });

function getYearMonth(offset = 0) {
const d = new Date();
d.setMonth(d.getMonth() + offset);
return d.toISOString().slice(0, 7); // YYYY-MM
}

async function findIssueByExactTitle(title) {
for (let page = 1; page < 50; page++) {
const resp = await octokit.request("GET /repos/{owner}/{repo}/issues", {
owner: repoOwner,
repo: repoName,
state: "all",
per_page: 100,
page,
});
if (!resp.data.length) break;
const hit = resp.data.find((i) => i.title === title);
if (hit) return hit;
if (resp.data.length < 100) break;
}
return null;
}

async function main() {
const currentYM = getYearMonth(0);
const previousYM = getYearMonth(-1);
const currentTitle = `GitHub Triage: ${currentYM}`;
const previousTitle = `GitHub Triage: ${previousYM}`;

// Close previous month issue if open
const prevIssue = await findIssueByExactTitle(previousTitle);
if (prevIssue && prevIssue.state === "open") {
await octokit.request(
"PATCH /repos/{owner}/{repo}/issues/{issue_number}",
{
owner: repoOwner,
repo: repoName,
issue_number: prevIssue.number,
state: "closed",
}
);
console.log(`Closed previous triage issue #${prevIssue.number}`);
} else {
console.log(`No open previous triage issue (title: ${previousTitle})`);
}

// Ensure current month issue exists
const currIssue = await findIssueByExactTitle(currentTitle);
if (currIssue) {
console.log(`Current triage issue already exists: #${currIssue.number}`);
return;
}

const body = `
### Monthly GitHub Triage – ${currentYM}

Automatically generated tracking issue for ${currentYM}.

#### Purpose
- Collect newly opened issues for classification.
- Track placeholders (titles containing \`[REPLACE_WITH_MODULE_TITLE]\`).
- Identify consolidation / closure candidates.

#### Actions
- Apply governance labels.
- Escalate support or experience issues.
- Prepare weekly summaries (see companion workflow).

:octocat: :copilot: Created automatically.
`.trim();

const created = await octokit.request("POST /repos/{owner}/{repo}/issues", {
owner: repoOwner,
repo: repoName,
title: currentTitle,
body,
labels: ["triage"],
});
console.log(`Created new triage issue #${created.data.number}`);
}

main().catch((err) => {
console.error(err);
process.exit(1);
}); No newline at end of file
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The JavaScript filename uses an incorrect naming convention. The file is named github_scripts_ensureMonthlyTriage.js but all workflows and the composite action reference it as .github/scripts/ensureMonthlyTriage.js. The file should either be renamed to ensureMonthlyTriage.js and placed in a .github/scripts/ directory, or all references should be updated to match this unconventional naming pattern.

Copilot uses AI. Check for mistakes.
page
});
const items = resp.data.items || [];
allItems = allItems.concat(items);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The GitHub Search API endpoint search.issuesAndPullRequests returns both issues and pull requests. Since the query string includes is:issue, the results should only contain issues. However, the code doesn't filter out pull requests explicitly. While the query should handle this, it's safer to add an explicit filter (allItems = allItems.concat(items.filter(item => !item.pull_request))) to ensure pull requests are excluded, especially given that the workflow is specifically for issue triage.

Suggested change
allItems = allItems.concat(items);
allItems = allItems.concat(items.filter(item => !item.pull_request));

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +8
"triage": "node .github/scripts/ensureMonthlyTriage.js",
"weekly": "node .github/scripts/weeklyTriageComment.js"
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The npm scripts reference .github/scripts/ path which doesn't match the actual script filenames. The scripts are named github_scripts_ensureMonthlyTriage.js and github_scripts_weeklyTriageComment.js, but these npm scripts expect them to be at .github/scripts/ensureMonthlyTriage.js and .github/scripts/weeklyTriageComment.js. This mismatch will cause the npm scripts to fail. The script paths should be updated to match the actual file locations, or the files should be renamed and moved to match these paths.

Suggested change
"triage": "node .github/scripts/ensureMonthlyTriage.js",
"weekly": "node .github/scripts/weeklyTriageComment.js"
"triage": "node .github/scripts/github_scripts_ensureMonthlyTriage.js",
"weekly": "node .github/scripts/github_scripts_weeklyTriageComment.js"

Copilot uses AI. Check for mistakes.
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.

1 participant