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"], - }); + //}); } }