Skip to content

Conversation

@dmitriyastapov
Copy link


Description of Change

This PR introduces a GitHub Action workflow that helps manage stale issues in the repository.
The bot runs daily and performs the following tasks:

  • Skips pull requests and issues labeled as to-be-discussed
  • Automatically closes issues labeled awaiting-response or those without assignees after 90+ days of inactivity
  • Sends a reminder comment on assigned issues that have been inactive for over 90 days (without re-sending it more than once a week)
  • Avoids duplicate comments and respects special labels
  • Moves issues labeled 'questions' to GitHub Discussions

Added a dry-run mode that lets you preview all actions without making any changes, to run it write "1" in the "Run workflow" input filed

Test Scenarios

The script and workflow were tested on GitHub Issues in my own fork.

Related links

Testing forked repo

(eg. Closes #number of issue)

@CLAassistant
Copy link

CLAassistant commented Dec 12, 2025

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link
Contributor

Messages
📖 🎉 Good Job! All checks are passing!

👋 Hello dmitriyastapov, we appreciate your contribution to this project!


📘 Please review the project's Contributions Guide for key guidelines on code, documentation, testing, and more.

🖊️ Please also make sure you have read and signed the Contributor License Agreement for this project.

Click to see more instructions ...


This automated output is generated by the PR linter DangerJS, which checks if your Pull Request meets the project's requirements and helps you fix potential issues.

DangerJS is triggered with each push event to a Pull Request and modify the contents of this comment.

Please consider the following:
- Danger mainly focuses on the PR structure and formatting and can't understand the meaning behind your code or changes.
- Danger is not a substitute for human code reviews; it's still important to request a code review from your colleagues.
- To manually retry these Danger checks, please navigate to the Actions tab and re-run last Danger workflow.

Review and merge process you can expect ...


We do welcome contributions in the form of bug reports, feature requests and pull requests.

1. An internal issue has been created for the PR, we assign it to the relevant engineer.
2. They review the PR and either approve it or ask you for changes or clarifications.
3. Once the GitHub PR is approved we do the final review, collect approvals from core owners and make sure all the automated tests are passing.
- At this point we may do some adjustments to the proposed change, or extend it by adding tests or documentation.
4. If the change is approved and passes the tests it is merged into the default branch.

Generated by 🚫 dangerJS against 254c109

@lucasssvaz lucasssvaz added Type: CI & Testing Related to continuous integration, automated testing, or test infrastructure. Status: Review needed Issue or PR is awaiting review labels Dec 12, 2025
@lucasssvaz lucasssvaz requested a review from Copilot December 12, 2025 17:00
Copy link
Contributor

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 GitHub Actions workflow to manage stale issues in the repository. The bot runs daily at 4 AM UTC and can also be triggered manually with an optional dry-run mode for testing. It processes open issues based on inactivity (90+ days) and applies different actions depending on issue status, labels, and assignment.

Key changes:

  • Automated closure of unassigned or "awaiting-response" issues after 90+ days of inactivity
  • Friendly reminder comments to assignees on inactive but assigned issues
  • Automatic labeling of question-type issues for migration to GitHub Discussions

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
.github/workflows/backlog-bot.yml GitHub Actions workflow that schedules daily runs at 4 AM UTC with manual trigger support and dry-run capability
.github/scripts/backlog-cleanup.js Core logic implementing issue triage, closure, reminder, and discussion migration behaviors with pagination support

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

Comment on lines +189 to +195
const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
);
if (recentFriendlyReminder) {
totalSkipped++;
continue;
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The logic for checking recent friendly reminders is incomplete. The code finds a reminder comment but doesn't verify if it was posted within the last 7 days as documented in the file header. This means duplicate reminders will still be sent as long as they're more than 7 days apart, but the current implementation skips the issue entirely if ANY friendly reminder comment exists, regardless of age. Add a date check to verify the reminder is within the last 7 days, similar to the documented behavior.

Suggested change
const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
);
if (recentFriendlyReminder) {
totalSkipped++;
continue;
// Find the most recent friendly reminder comment from github-actions[bot]
const friendlyReminders = comments
.filter(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
)
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
const mostRecentReminder = friendlyReminders.length > 0 ? friendlyReminders[0] : null;
const now = new Date();
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
if (mostRecentReminder) {
const reminderDate = new Date(mostRecentReminder.created_at);
if (now - reminderDate < sevenDaysMs) {
totalSkipped++;
continue;
}

Copilot uses AI. Check for mistakes.
if (issue.labels.some(label => label.name === questionLabel)) {
const marked = await addMoveToDiscussionLabel(github, owner, repo, issue, isDryRun);
if (marked) totalMarkedToMigrate++;
continue; // Do not apply reminder logic
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

After marking a question issue for migration to discussions, the script continues to the next issue. However, if the issue has been inactive for 90+ days and is assigned, it should potentially still receive a reminder. The current logic skips all further processing for question issues. Consider whether question issues that are assigned and inactive should also receive reminders, or clarify the intended behavior in the documentation.

Suggested change
continue; // Do not apply reminder logic
// Do not skip further processing; allow reminder logic for assigned, inactive question issues

Copilot uses AI. Check for mistakes.
This issue has had no activity for ${daysSinceUpdate} days. If it's still relevant:
- Please provide a status update
- Add any blocking details
- Or label it 'awaiting-response' if you're waiting on something
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The label name in the comment text ('awaiting-response') doesn't match the actual label name defined in closeLabels ('Status: Awaiting Response'). This inconsistency could confuse users who are looking for the exact label name to apply. Consider using the actual label name or referencing it from the closeLabels array.

Suggested change
- Or label it 'awaiting-response' if you're waiting on something
- Or label it 'Status: Awaiting Response' if you're waiting on something

Copilot uses AI. Check for mistakes.
Comment on lines +169 to +198
let comments = [];
try {
let page = 1;
while (true) {
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 100,
page,
});
if (!data || data.length === 0) break;
comments.push(...data);
if (data.length < 100) break;
page++;
}
} catch (err) {
console.error(`Error fetching comments for issue #${issue.number}:`, err);
}

const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
);
if (recentFriendlyReminder) {
totalSkipped++;
continue;
}

if (shouldSendReminder(issue, exemptLabels, closeLabels)) {
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Comments are fetched for every issue that passes the initial checks, even when they're not needed. The comments are only used for checking friendly reminders on assigned issues without exempt/close labels. Consider moving the comment fetching logic inside the shouldSendReminder check to avoid unnecessary API calls for issues that will be closed or don't need reminders. This could significantly reduce API rate limit consumption, especially on repositories with many issues.

Suggested change
let comments = [];
try {
let page = 1;
while (true) {
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 100,
page,
});
if (!data || data.length === 0) break;
comments.push(...data);
if (data.length < 100) break;
page++;
}
} catch (err) {
console.error(`Error fetching comments for issue #${issue.number}:`, err);
}
const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
);
if (recentFriendlyReminder) {
totalSkipped++;
continue;
}
if (shouldSendReminder(issue, exemptLabels, closeLabels)) {
if (shouldSendReminder(issue, exemptLabels, closeLabels)) {
// Fetch comments only if we might send a reminder
let comments = [];
try {
let page = 1;
while (true) {
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 100,
page,
});
if (!data || data.length === 0) break;
comments.push(...data);
if (data.length < 100) break;
page++;
}
} catch (err) {
console.error(`Error fetching comments for issue #${issue.number}:`, err);
}
const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder')
);
if (recentFriendlyReminder) {
totalSkipped++;
continue;
}

Copilot uses AI. Check for mistakes.
* exempt labels that have been inactive for 90+ days.
* - Avoids sending duplicate Friendly Reminder comments if one was
* posted within the last 7 days.
* - Marks issues labeled 'questions' to 'Move to Discussion'.
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The documentation states that the script "Marks issues labeled 'questions' to 'Move to Discussion'" but doesn't explain what happens after marking. It would be helpful to clarify that this is only adding a label and that actual migration to discussions would need to be handled separately (either manually or by another automation).

Suggested change
* - Marks issues labeled 'questions' to 'Move to Discussion'.
* - Marks issues labeled 'questions' by adding the 'Move to Discussion' label.
* (Actual migration to Discussions must be handled separately, either manually or by another automation.)

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +128
if (issue.labels.some(label => exemptLabels.includes(label.name))) {
totalSkipped++;
continue;
}
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Issues with exempt labels are skipped before checking the threshold days, but the documentation and intended behavior suggest that exempt labels should prevent reminders and closures, not prevent staleness checks entirely. An issue with an exempt label that also has 'Status: Awaiting Response' or is unassigned might still need to be processed. Consider checking exempt labels only where they're actually relevant (in the reminder logic) rather than at the top of the loop.

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +97
const thresholdDays = 90;
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation', 'Move to Discussion'];
const closeLabels = ['Status: Awaiting Response'];
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The threshold of 90 days and the list of exempt/close labels are hardcoded. Consider making these configurable through environment variables or workflow inputs to allow easier adjustment without modifying the script. This would improve maintainability and flexibility for different repository needs.

Suggested change
const thresholdDays = 90;
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation', 'Move to Discussion'];
const closeLabels = ['Status: Awaiting Response'];
// Allow configuration via environment variables, fallback to defaults
const thresholdDays = process.env.BACKLOG_CLEANUP_THRESHOLD_DAYS
? parseInt(process.env.BACKLOG_CLEANUP_THRESHOLD_DAYS, 10)
: 90;
const exemptLabels = process.env.BACKLOG_CLEANUP_EXEMPT_LABELS
? process.env.BACKLOG_CLEANUP_EXEMPT_LABELS.split(',').map(l => l.trim())
: ['Status: Community help needed', 'Status: Needs investigation', 'Move to Discussion'];
const closeLabels = process.env.BACKLOG_CLEANUP_CLOSE_LABELS
? process.env.BACKLOG_CLEANUP_CLOSE_LABELS.split(',').map(l => l.trim())
: ['Status: Awaiting Response'];

Copilot uses AI. Check for mistakes.
module.exports = async ({ github, context, dryRun }) => {
const now = new Date();
const thresholdDays = 90;
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation', 'Move to Discussion'];
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The 'Move to Discussion' label is included in exemptLabels, which means issues that have already been marked for migration will be skipped entirely in future runs. This prevents the bot from ever closing these issues even if they meet closure criteria. Consider whether issues marked for discussion migration should still be subject to closure after extended inactivity, or remove 'Move to Discussion' from the exempt list.

Suggested change
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation', 'Move to Discussion'];
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation'];

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

Status: Review needed Issue or PR is awaiting review Type: CI & Testing Related to continuous integration, automated testing, or test infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants