Skip to content

Close stale issues and PRs #349

Close stale issues and PRs

Close stale issues and PRs #349

Workflow file for this run

name: 'Close stale issues and PRs'
on:
workflow_dispatch:
schedule:
- cron: '17 1 * * *'
issue_comment:
types:
- created
jobs:
stale:
if: github.event_name != 'issue_comment'
runs-on: ubuntu-latest
permissions:
actions: write
issues: write
pull-requests: write
steps:
- uses: actions/stale@v11
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
days-before-issue-stale: 30
days-before-pr-stale: 45
days-before-issue-close: 5
days-before-pr-close: 10
stale-issue-label: 'no-issue-activity'
exempt-issue-labels: 'work-in-progress,no-stale'
stale-pr-label: 'no-pr-activity'
exempt-pr-labels: 'work-in-progress,no-stale'
operations-per-run: 1000
unstale:
if: |
github.event_name == 'issue_comment' &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.issue.labels.*.name, 'work-in-progress') &&
!contains(github.event.issue.labels.*.name, 'no-stale') &&
(contains(github.event.issue.labels.*.name, 'no-issue-activity') ||
contains(github.event.issue.labels.*.name, 'no-pr-activity'))
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Reopen or unstale after a human comment
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue = context.payload.issue;
const isPullRequest = Boolean(issue.pull_request);
const staleLabel = isPullRequest ? 'no-pr-activity' : 'no-issue-activity';
const labels = issue.labels.map((label) => label.name);
const apiHeaders = { 'X-GitHub-Api-Version': '2026-03-10' };
if (!labels.includes(staleLabel)) {
core.info(`#${issue.number} does not have the expected '${staleLabel}' label.`);
return;
}
if (issue.state === 'closed' && isPullRequest && issue.pull_request.merged_at) {
core.info(`PR #${issue.number} is merged; leaving it closed.`);
} else if (issue.state === 'closed') {
if (!isPullRequest && issue.state_reason !== 'not_planned') {
core.info(`#${issue.number} was not closed as not planned; leaving it closed.`);
return;
}
const route = isPullRequest
? 'PATCH /repos/{owner}/{repo}/pulls/{pull_number}'
: 'PATCH /repos/{owner}/{repo}/issues/{issue_number}';
const numberField = isPullRequest
? { pull_number: issue.number }
: { issue_number: issue.number, state_reason: 'reopened' };
await github.request(route, {
owner,
repo,
...numberField,
state: 'open',
headers: apiHeaders
});
core.info(`Reopened ${isPullRequest ? 'PR' : 'issue'} #${issue.number}.`);
}
await github.request(
'DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}',
{
owner,
repo,
issue_number: issue.number,
name: staleLabel,
headers: apiHeaders
}
);
core.info(`Removed '${staleLabel}' from #${issue.number}.`);