Skip to content

Conversation

@SamuelFialka
Copy link

@SamuelFialka SamuelFialka commented Jun 25, 2025

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

The purpose of this automation is to keep the issue backlog clean, manageable, and up-to-date.
Feel free to suggest adjustments to label logic, thresholds, or wording in comments.

Tests scenarios

The script and workflow were successfully tested on GitHub Issues in my own fork. Behavior such as issue closure and reminder comments worked as expected.

Related links

Testing forked repo

(eg. Closes #number of issue)

@SamuelFialka SamuelFialka requested a review from lucasssvaz as a code owner June 25, 2025 11:32
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Samuel Fialka seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 25, 2025

Messages
📖 🎉 Good Job! All checks are passing!

👋 Hello SamuelFialka, 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 b012dab

@lucasssvaz lucasssvaz requested a review from Copilot June 25, 2025 12:28

This comment was marked as outdated.

@lucasssvaz lucasssvaz added the Type: CI & Testing Related to continuous integration, automated testing, or test infrastructure. label Jun 25, 2025
@P-R-O-C-H-Y P-R-O-C-H-Y added the Status: Review needed Issue or PR is awaiting review label Jun 27, 2025
@SamuelFialka SamuelFialka force-pushed the feat/backlog-bot branch 2 times, most recently from aac1f34 to f409eb2 Compare July 28, 2025 10:17
@lucasssvaz lucasssvaz requested a review from Copilot July 30, 2025 21:42
@lucasssvaz lucasssvaz added the Status: Pending CLA ⚠️ Contributor is required to sign the CLA label Jul 30, 2025
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 for managing stale issues in the repository backlog. The bot runs daily to keep issues organized and up-to-date by handling different types of inactive issues.

Key changes:

  • Adds a daily scheduled workflow that processes open issues older than 90 days
  • Automatically closes issues with specific labels or without assignees
  • Sends reminder comments to assigned issues and migrates questions to discussions

Reviewed Changes

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

File Description
.github/workflows/backlog-bot.yml Defines the GitHub Actions workflow that runs daily and executes the cleanup script
.github/scripts/backlog-cleanup.js Contains the main logic for processing issues, sending reminders, closing stale issues, and migrating questions to discussions

@github-actions
Copy link
Contributor

Memory usage test (comparing PR against master branch)

The table below shows the summary of memory usage change (decrease - increase) in bytes and percentage for each target.

MemoryFLASH [bytes]FLASH [%]RAM [bytes]RAM [%]
TargetDECINCDECINCDECINCDECINC
ESP32C5000.000.00000.000.00
ESP32P4000.000.00000.000.00
ESP32S3000.000.00000.000.00
ESP32S2000.000.00000.000.00
ESP32C3000.000.00000.000.00
ESP32C6000.000.00000.000.00
ESP32H2000.000.00000.000.00
ESP32000.000.00000.000.00
Click to expand the detailed deltas report [usage change in BYTES]
TargetESP32C5ESP32P4ESP32S3ESP32S2ESP32C3ESP32C6ESP32H2ESP32
ExampleFLASHRAMFLASHRAMFLASHRAMFLASHRAMFLASHRAMFLASHRAMFLASHRAMFLASHRAM
EEPROM/examples/eeprom_class00--00--00000000
EEPROM/examples/eeprom_extra00--00--00000000
EEPROM/examples/eeprom_write00--00--00000000
ESP32/examples/AnalogOut/LEDCFade00--00--00000000
ESP32/examples/AnalogOut/LEDCGammaFade00--------0000--
ESP32/examples/AnalogOut/LEDCSingleChannel00--00--00000000
ESP32/examples/AnalogOut/LEDCSoftwareFade00--00--00000000
ESP32/examples/AnalogOut/SigmaDelta000000--00000000
ESP32/examples/AnalogOut/ledcFrequency000000--00000000
ESP32/examples/AnalogOut/ledcWrite_RGB000000--00000000
ESP32/examples/AnalogRead000000--00000000
ESP32/examples/AnalogReadContinuous0000000000000000
ESP32/examples/ArduinoStackSize0000000000000000
ESP32/examples/CI/CIBoardsTest0000000000000000
ESP32/examples/ChipID/GetChipID0000000000000000
ESP32/examples/DeepSleep/TimerWakeUp000000000000--00
ESP32/examples/FreeRTOS/BasicMultiThreading0000000000000000
ESP32/examples/FreeRTOS/Mutex0000000000000000
ESP32/examples/FreeRTOS/Queue0000000000000000
ESP32/examples/FreeRTOS/Semaphore0000000000000000
ESP32/examples/GPIO/BlinkRGB0000000000000000
ESP32/examples/GPIO/FunctionalInterrupt0000000000000000
ESP32/examples/GPIO/FunctionalInterruptLambda0000000000000000
ESP32/examples/GPIO/FunctionalInterruptStruct0000000000000000
ESP32/examples/GPIO/GPIOInterrupt0000000000000000
ESP32/examples/HWCDC_Events000000--000000--
ESP32/examples/MacAddress/GetMacAddress0000000000000000
ESP32/examples/RMT/Legacy_RMT_Driver_Compatible0000000000000000
ESP32/examples/RMT/RMTCallback0000000000000000
ESP32/examples/RMT/RMTLoopback0000000000000000
ESP32/examples/RMT/RMTReadXJT0000000000000000
ESP32/examples/RMT/RMTWrite_RGB_LED0000000000000000
ESP32/examples/RMT/RMT_CPUFreq_Test0000000000000000
ESP32/examples/RMT/RMT_EndOfTransmissionState0000000000000000
ESP32/examples/RMT/RMT_LED_Blink0000000000000000
ESP32/examples/ResetReason/ResetReason0000000000000000
ESP32/examples/ResetReason/ResetReason20000000000000000
ESP32/examples/Serial/BaudRateDetect_Demo0000000000000000
ESP32/examples/Serial/OnReceiveError_BREAK_Demo0000000000000000
ESP32/examples/Serial/OnReceive_Demo0000000000000000
ESP32/examples/Serial/RS485_Echo_Demo000000000000--00
ESP32/examples/Serial/RxFIFOFull_Demo000000000000--00
ESP32/examples/Serial/RxTimeout_Demo000000000000--00
ESP32/examples/Serial/Serial_All_CPU_Freqs000000000000--00
ESP32/examples/Serial/Serial_STD_Func_OnReceive000000000000--00
ESP32/examples/Serial/onReceiveExample000000000000--00
ESP32/examples/Template/ExampleTemplate000000000000--00
ESP32/examples/Time/SimpleTime000000000000----
ESP32/examples/Timer/RepeatTimer000000000000----
ESP32/examples/Timer/WatchdogTimer000000000000----
ESP32/examples/Utilities/HEXBuilder00--00000000----
ESP32/examples/Utilities/MD5Builder00--00000000----
ESP32/examples/Utilities/SHA1Builder00--00000000----
ESP_I2S/examples/ES8388_loopback00--0000--00----
ESP_I2S/examples/Simple_tone00--0000--00----
ESP_NOW/examples/ESP_NOW_Broadcast_Master00--0000--00----
ESP_NOW/examples/ESP_NOW_Broadcast_Slave00--0000--00----
ESP_NOW/examples/ESP_NOW_Network00----00--00----
ESP_NOW/examples/ESP_NOW_Serial00----00--00----
ESPmDNS/examples/mDNS-SD_Extended00----00--00----
ESPmDNS/examples/mDNS_Web_Server00----00--00----
Ethernet/examples/ETH_W5500_Arduino_SPI00----00--00----
Ethernet/examples/ETH_W5500_IDF_SPI00----00--------
Ethernet/examples/ETH_WIFI_BRIDGE00----00--------
Zigbee/examples/Zigbee_Pressure_Flow_Sensor00--------00----
Zigbee/examples/Zigbee_Range_Extender00--00000000--00
Zigbee/examples/Zigbee_Scan_Networks00--------00----
Zigbee/examples/Zigbee_Temp_Hum_Sensor_Sleepy00--------00----
Zigbee/examples/Zigbee_Temperature_Sensor00--------00----
Zigbee/examples/Zigbee_Thermostat00--000000000000
Zigbee/examples/Zigbee_Vibration_Sensor00--------0000--
Zigbee/examples/Zigbee_Wind_Speed_Sensor00--------0000--
Zigbee/examples/Zigbee_Window_Covering00--------0000--
ESP32/examples/DeepSleep/TouchWakeUp--000000------00
ESP32/examples/TWAI/TWAIreceive--0000000000--00
ESP32/examples/TWAI/TWAItransmit--0000000000--00
ESP32/examples/Touch/TouchInterrupt--000000--------
ESP32/examples/Touch/TouchRead--000000--------
WiFi/examples/WiFiTelnetToSerial--00------------
WiFi/examples/WiFiUDPClient--00------------
Wire/examples/WireMaster--00------------
Wire/examples/WireScan--00------------
Wire/examples/WireSlave--00------------
Wire/examples/WireSlaveFunctionalCallback--00------------
ESP32/examples/Camera/CameraWebServer (1)----0000------00
ESP32/examples/Camera/CameraWebServer (2)----0000------00
ESP32/examples/Camera/CameraWebServer (3)----00----------
ESP32/examples/DeepSleep/ExternalWakeUp----0000------00
ESP_I2S/examples/Record_to_WAV----00----------
Zigbee/examples/Zigbee_Analog_Input_Output----00----------
Zigbee/examples/Zigbee_Color_Dimmer_Switch----00--------00
Zigbee/examples/Zigbee_Electrical_AC_Sensor----00--00----00
Zigbee/examples/Zigbee_Electrical_AC_Sensor_MultiPhase----00--00----00
Zigbee/examples/Zigbee_Fan_Control----000000----00
Zigbee/examples/Zigbee_Gateway----000000----00
Zigbee/examples/Zigbee_On_Off_MultiSwitch----000000----00
Zigbee/examples/Zigbee_On_Off_Switch----000000----00
Zigbee/examples/Zigbee_Power_Outlet----00000000--00
FFat/examples/FFat_Test------00--------
BLE/examples/iBeacon--------00--00--
DNSServer/examples/CaptivePortal--------00----00
BluetoothSerial/examples/DiscoverConnect--------------00
BluetoothSerial/examples/GetLocalMAC--------------00
BluetoothSerial/examples/SerialToSerialBT--------------00
BluetoothSerial/examples/SerialToSerialBTM--------------00
BluetoothSerial/examples/SerialToSerialBT_Legacy--------------00
BluetoothSerial/examples/SerialToSerialBT_SSP--------------00
BluetoothSerial/examples/bt_classic_device_discovery--------------00
BluetoothSerial/examples/bt_remove_paired_devices--------------00
ESP32/examples/DeepSleep/SmoothBlink_ULP_Code--------------00

Comment on lines 52 to 91
async function migrateToDiscussion(github, owner, repo, issue) {
const discussionCategory = 'Q&A';

const { data: categories } = await github.rest.discussions.listCategories({
owner,
repo,
});

const category = categories.find(cat =>
cat.name.toLowerCase() === discussionCategory.toLowerCase()
);

if (!category) {
throw new Error(`Discussion category '${discussionCategory}' not found.`);
}

const { data: discussion } = await github.rest.discussions.create({
owner,
repo,
title: issue.title,
body: `Originally created by @${issue.user.login} in #${issue.number}\n\n---\n\n${issue.body}`,
category_id: category.id,
});

await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `💬 This issue was moved to [Discussions](${discussion.html_url}) for better visibility.`,
});

await github.rest.issues.update({
owner,
repo,
issue_number: issue.number,
state: 'closed',
});

return discussion.html_url;
}
Copy link
Member

Choose a reason for hiding this comment

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

I think this will cause the discussion to lose its comments (unlike when clicking Convert to Discussion). I don't see any API available to do this though. IDK what would be the best approach here.

Copy link
Author

Choose a reason for hiding this comment

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

You are right, there is no API available. Maybe let's keep this step as a manual action for a human to navigate the reporter to the Discussion instead?

The bot could also copy and paste every comment from Issues to Discussion, but this could be more confusing.

Copy link
Member

Choose a reason for hiding this comment

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

Could we also implement a dry-run mode toggled by an input from a workflow dispatch ?

Copy link
Author

Choose a reason for hiding this comment

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

I performed a dry run on the forked repository linked in the issue description. Do you want to do dryrun on regular basis?

@lucasssvaz
Copy link
Member

Did you test it in some fork to check if everything is working fine ? If so, could you post the link ?


for (const issue of issues) {
const isAssigned = issue.assignees && issue.assignees.length > 0;
const lastUpdate = new Date(issue.updated_at);
Copy link
Member

Choose a reason for hiding this comment

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

Do you know what triggers this updated at ? If I change a label, will this moment be considered the last update ?

Copy link
Author

Choose a reason for hiding this comment

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

Triggers are changing labels, editing the title, body, assignees, comments, closing or reopening the issue.

const recentFriendlyReminder = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('⏰ Friendly Reminder') &&
(now - new Date(comment.created_at)) < sevenDays
Copy link
Member

@lucasssvaz lucasssvaz Jul 31, 2025

Choose a reason for hiding this comment

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

Why is this checking if "recent" is seven days ? Once the comment is posted, won't the daysSinceUpdate become 0 and next reminder only be posted after 90 more days ? (this is what we want it to do)

Copy link
Author

Choose a reason for hiding this comment

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

You are right, I've removed this line.

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

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


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

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

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


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +171 to +177
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 50,
});
comments = data;
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The comment fetching uses per_page: 50 without pagination. For issues with more than 50 comments, recent 'Friendly Reminder' comments might not be fetched, causing duplicate reminders to be sent. Implement pagination or fetch comments in reverse chronological order to ensure recent comments are captured.

Suggested change
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 50,
});
comments = data;
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++;
}

Copilot uses AI. Check for mistakes.
if (!category) {
throw new Error(`Discussion category '${discussionCategory}' not found.`);
}
const { data: discussion } = await github.rest.discussions.create({
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The GitHub REST API does not have a github.rest.discussions.create endpoint. Discussion creation requires the GraphQL API. This will cause the migration feature to fail at runtime. Use the GraphQL API with the createDiscussion mutation instead.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +122
const { data } = await github.rest.discussions.listCategories({ owner, repo });
categories = data;
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The GitHub REST API does not have a github.rest.discussions.listCategories endpoint. Fetching discussion categories requires the GraphQL API. This will cause the category lookup to fail. Use the GraphQL API to query the repository's discussion categories.

Suggested change
const { data } = await github.rest.discussions.listCategories({ owner, repo });
categories = data;
const result = await github.graphql(`
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
discussionCategories(first: 100) {
nodes {
id
name
}
}
}
}
`, { owner, repo });
categories = result.repository.discussionCategories.nodes;

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: Pending CLA ⚠️ Contributor is required to sign the CLA 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.

4 participants