From 26b65b3de9e3ef84af9edc508a274561adc2684c Mon Sep 17 00:00:00 2001
From: Nikhil Viswanath Sivakumar
<68182521+nil-is-all@users.noreply.github.com>
Date: Wed, 20 Aug 2025 18:44:28 -0500
Subject: [PATCH 1/4] Create stale.yml workflow to label stale PRs
---
.github/workflows/stale | 149 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 149 insertions(+)
create mode 100644 .github/workflows/stale
diff --git a/.github/workflows/stale b/.github/workflows/stale
new file mode 100644
index 00000000000..4f39416a7b4
--- /dev/null
+++ b/.github/workflows/stale
@@ -0,0 +1,149 @@
+# The behavior is:
+# - If a PR is not labeled stale, after 60 days inactivity label the PR as stale and comment about it.
+# - If a PR is labeled stale, after 30 days inactivity close the PR.
+# - `high priority` and `no-stale` PRs are exempt.
+
+name: Close stale pull requests
+
+on:
+ schedule:
+ # Run hourly.
+ - cron: 30 * * * *
+ workflow_dispatch:
+
+jobs:
+ stale:
+ if: ${{ github.repository == 'pytorch/executorch' }}
+ runs-on: linux.large
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ with:
+ script: |
+ // Do some dumb retries on requests.
+ const retries = 7;
+ const baseBackoff = 100;
+ const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
+ github.hook.wrap('request', async (request, options) => {
+ for (let attempt = 1; attempt <= retries; attempt++) {
+ try {
+ return await request(options);
+ } catch (err) {
+ if (attempt < retries) {
+ core.warning(`Request getting retried. Attempt: ${attempt}`);
+ await sleep(baseBackoff * Math.pow(2, attempt));
+ continue;
+ }
+ throw err;
+ }
+ }
+ });
+
+ const MAX_API_REQUESTS = 100;
+
+ // If a PRs not labeled stale, label them stale after no update for 60 days.
+ const STALE_LABEL_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 60;
+ // For PRs already labeled stale, close after not update for 30 days.
+ const STALE_CLOSE_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 30;
+
+ const STALE_MESSAGE =
+ "Looks like this PR hasn't been updated in a while so we're going to go ahead and mark this as `Stale`.
" +
+ "Feel free to remove the `Stale` label if you feel this was a mistake.
" +
+ "If you are unable to remove the `Stale` label please contact a maintainer in order to do so.
" +
+ "If you want the bot to never mark this PR stale again, add the `no-stale` label.
" +
+ "`Stale` pull requests will automatically be closed after 30 days of inactivity.
";
+
+ let numAPIRequests = 0;
+ let numProcessed = 0;
+
+ async function processPull(pull) {
+ core.info(`[${pull.number}] URL: ${pull.html_url}`);
+ numProcessed += 1;
+ const labels = pull.labels.map((label) => label.name);
+
+ // Skip if certain labels are present.
+ if (labels.includes("no-stale") || labels.includes("high priority")) {
+ core.info(`[${pull.number}] Skipping because PR has an exempting label.`);
+ return false;
+ }
+
+ // Check if the PR is stale, according to our configured thresholds.
+ let staleThresholdMillis;
+ if (labels.includes("Stale")) {
+ core.info(`[${pull.number}] PR is labeled stale, checking whether we should close it.`);
+ staleThresholdMillis = STALE_CLOSE_THRESHOLD_MS;
+ } else {
+ core.info(`[${pull.number}] Checking whether to label PR as stale.`);
+ staleThresholdMillis = STALE_LABEL_THRESHOLD_MS;
+ }
+
+ const millisSinceLastUpdated =
+ new Date().getTime() - new Date(pull.updated_at).getTime();
+
+ if (millisSinceLastUpdated < staleThresholdMillis) {
+ core.info(`[${pull.number}] Skipping because PR was updated recently`);
+ return false;
+ }
+
+ // At this point, we know we should do something.
+ // For PRs already labeled stale, close them.
+ if (labels.includes("Stale")) {
+ core.info(`[${pull.number}] Closing PR.`);
+ numAPIRequests += 1;
+ await github.rest.issues.update({
+ owner: "pytorch",
+ repo: "executorch",
+ issue_number: pull.number,
+ state: "closed",
+ });
+ } else {
+ // For PRs not labeled stale, label them stale.
+ core.info(`[${pull.number}] Labeling PR as stale.`);
+
+ numAPIRequests += 1;
+ await github.rest.issues.createComment({
+ owner: "pytorch",
+ repo: "executorch",
+ issue_number: pull.number,
+ body: STALE_MESSAGE,
+ });
+
+ numAPIRequests += 1;
+ await github.rest.issues.addLabels({
+ owner: "pytorch",
+ repo: "executorch",
+ issue_number: pull.number,
+ labels: ["Stale"],
+ });
+ }
+ }
+
+ for await (const response of github.paginate.iterator(
+ github.rest.pulls.list,
+ {
+ owner: "pytorch",
+ repo: "executorch",
+ state: "open",
+ sort: "created",
+ direction: "asc",
+ per_page: 100,
+ }
+ )) {
+ numAPIRequests += 1;
+ const pulls = response.data;
+ // Awaiting in a loop is intentional here. We want to serialize execution so
+ // that log groups are printed correctl
+ for (const pull of pulls) {
+ if (numAPIRequests > MAX_API_REQUESTS) {
+ core.warning("Max API requests exceeded, exiting.");
+ process.exit(0);
+ }
+ await core.group(`Processing PR #${pull.number}`, async () => {
+ await processPull(pull);
+ });
+ }
+ }
+ core.info(`Processed ${numProcessed} PRs total.`);
From e384cbe4cb8fe5b6697cd616acef16700d86eaaa Mon Sep 17 00:00:00 2001
From: Nikhil Viswanath Sivakumar
<68182521+nil-is-all@users.noreply.github.com>
Date: Thu, 21 Aug 2025 20:29:57 -0500
Subject: [PATCH 2/4] changed to run daily instead of hourly
---
.github/workflows/stale | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/stale b/.github/workflows/stale
index 4f39416a7b4..7ae735a2652 100644
--- a/.github/workflows/stale
+++ b/.github/workflows/stale
@@ -7,8 +7,8 @@ name: Close stale pull requests
on:
schedule:
- # Run hourly.
- - cron: 30 * * * *
+ # Run daily at 00:30 UTC.
+ - cron: '30 0 * * *'
workflow_dispatch:
jobs:
From 9bea956d5414bbab6b98f0a84e906700cb542824 Mon Sep 17 00:00:00 2001
From: Nikhil Viswanath Sivakumar
<68182521+nil-is-all@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:37:30 -0500
Subject: [PATCH 3/4] Commented update blocks to check if workflow works
expectedly
---
.github/workflows/stale | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/stale b/.github/workflows/stale
index 7ae735a2652..caf031f0458 100644
--- a/.github/workflows/stale
+++ b/.github/workflows/stale
@@ -93,30 +93,30 @@ jobs:
if (labels.includes("Stale")) {
core.info(`[${pull.number}] Closing PR.`);
numAPIRequests += 1;
- await github.rest.issues.update({
- owner: "pytorch",
- repo: "executorch",
- issue_number: pull.number,
- state: "closed",
+ //await github.rest.issues.update({
+ //owner: "pytorch",
+ //repo: "executorch",
+ //issue_number: pull.number,
+ //state: "closed",
});
} else {
// For PRs not labeled stale, label them stale.
core.info(`[${pull.number}] Labeling PR as stale.`);
numAPIRequests += 1;
- await github.rest.issues.createComment({
- owner: "pytorch",
- repo: "executorch",
- issue_number: pull.number,
- body: STALE_MESSAGE,
+ //await github.rest.issues.createComment({
+ //owner: "pytorch",
+ //repo: "executorch",
+ //issue_number: pull.number,
+ //body: STALE_MESSAGE,
});
numAPIRequests += 1;
- await github.rest.issues.addLabels({
- owner: "pytorch",
- repo: "executorch",
- issue_number: pull.number,
- labels: ["Stale"],
+ //await github.rest.issues.addLabels({
+ //owner: "pytorch",
+ //repo: "executorch",
+ //issue_number: pull.number,
+ //labels: ["Stale"],
});
}
}
From 743c01e9a338987735c872d86cea91919fa7192b Mon Sep 17 00:00:00 2001
From: Nikhil Viswanath Sivakumar
<68182521+nil-is-all@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:40:23 -0500
Subject: [PATCH 4/4] Update stale
---
.github/workflows/stale | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/stale b/.github/workflows/stale
index caf031f0458..bc3778da8d5 100644
--- a/.github/workflows/stale
+++ b/.github/workflows/stale
@@ -98,7 +98,7 @@ jobs:
//repo: "executorch",
//issue_number: pull.number,
//state: "closed",
- });
+ //});
} else {
// For PRs not labeled stale, label them stale.
core.info(`[${pull.number}] Labeling PR as stale.`);
@@ -109,7 +109,7 @@ jobs:
//repo: "executorch",
//issue_number: pull.number,
//body: STALE_MESSAGE,
- });
+ //});
numAPIRequests += 1;
//await github.rest.issues.addLabels({
@@ -117,7 +117,7 @@ jobs:
//repo: "executorch",
//issue_number: pull.number,
//labels: ["Stale"],
- });
+ //});
}
}