A GitHub Actions stale workflow builder that generates a ready-to-commit stale.yml
using the official actions/stale@v9 action. It marks inactive issues and pull requests as
stale, then closes them after a grace period — unless they carry a label you have marked as
exempt. The result is a self-maintaining issue tracker that does not drown in abandoned tickets.
How it works
The generated workflow runs on a schedule (a cron expression in UTC) and can also be
triggered manually via workflow_dispatch. On each run, actions/stale scans open issues
and PRs and compares each item’s last-activity time against days-before-stale. Items
past that threshold get the stale label and a comment. A second timer, days-before-close,
counts forward from when the item became stale; once it elapses, the item is closed with a
closing comment.
Exempt labels short-circuit the whole process: an item with pinned, security, or any
label you list is never touched. The job declares issues: write and pull-requests: write
permissions so it can apply labels, comment, and close — operations a read-only token cannot
perform.
Generated workflow structure
name: Stale
on:
schedule:
- cron: "0 1 * * *" # daily at 01:00 UTC
workflow_dispatch: # allow manual runs to preview behavior
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: >
This issue has been inactive for 60 days and is marked stale.
Add a comment to keep it open.
close-issue-message: >
This issue has been closed due to inactivity.
Reopen it if the problem persists.
stale-pr-message: >
This PR has been inactive for 60 days and is marked stale.
close-pr-message: >
This PR has been closed due to inactivity.
days-before-stale: 60
days-before-close: 7
exempt-issue-labels: "pinned,security,roadmap"
exempt-pr-labels: "pinned,do-not-close"
operations-per-run: 100
Understanding the timeline
The two timer settings combine, not overlap:
- An item untouched for 60 days gets the stale label and a comment.
- If no activity happens for another 7 days after that, the item is closed.
- So the total time from last activity to closure is 67 days.
Reopening a closed issue or adding a comment to a stale one resets the clock — the stale label is removed and the countdown starts over.
Choosing exempt labels wisely
Exempt labels protect items from the stale cycle entirely. Common candidates:
| Label | Reason to exempt |
|---|---|
pinned | Known issue deliberately kept open for visibility |
security | Security vulnerabilities should never auto-close |
roadmap | Intentional long-term tracking issues |
help wanted | Actively seeking contribution — premature to close |
blocked | Waiting on an upstream dependency or external factor |
Add these labels to issues that would be confusing or damaging to auto-close, and leave everyday bugs and feature requests to the stale cycle.
Tips and notes
- Keep
operations-per-runmodest on large repositories. The action burns API quota for every comment and label change, and a huge backlog can otherwise exhaust your hourly limit. - Set
days-before-closeto-1if you only want a stale label and a nudge, leaving the close decision to a human. - Run the workflow on
workflow_dispatchfirst to preview behaviour before trusting the schedule — the action logs exactly which items it would mark or close. - Schedule it during off-peak UTC hours (for example
0 1 * * *) so the notification burst does not land in the middle of your team’s workday.