If you’ve ever opened a repository’s pull request list and seen the same three PRs sitting there since last spring, you already know the problem. Review queues become graveyards for forgotten branches, abandoned experiments, and half-finished features. The cleaner solution is to automatically close stale PRs with GitHub Actions, using the official stale action. But a default configuration can do more harm than good—closing a valuable PR just because someone took a long vacation or a draft needed a second round of research. This quick fix walks you through a thoughtful, 2026-friendly setup that keeps the repository tidy without discarding meaningful work.
Why “Stale” PRs Accumulate and Why It Matters
Stale PRs don’t just look messy. They create a quiet tax on every collaborator who opens the list, wonders whether an idea was already implemented, and hesitates before creating an overlapping change. They also confuse automated tooling: code owners, changelogs, and branch protection rules can behave unexpectedly when an old PR still points to a branch that has diverged wildly from the default branch.
One reason PRs go stale is simple human inertia. A contributor opens a PR, gets busy, receives no immediate feedback, and forgets about it. Another reason is process-related: maintainers may be hesitant to close an old PR because it feels rude, or because they lack context about whether the work is still valuable. The stale action exists to remove that emotional burden. It applies a label, sends a comment, and then closes the PR after a grace period—all on a schedule that matches your team’s workflow.
The Baseline: Adding the Stale Action to Your Workflow
The core of this quick fix is a GitHub Actions workflow file. You can create one at .github/workflows/stale.yml in your repository. Here is a minimal starting point that reflects common practice for a small team:
name: Close stale PRs
on:
schedule:
- cron: '30 7 * * 1' # every Monday at 7:30 UTC
pull_request:
types: [opened, reopened, ready_for_review]
permissions:
contents: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-pr-message: 'This PR has been inactive for 60 days. If it is still relevant, please leave a comment or mark it as ready. Otherwise, it will close in 7 days.'
close-pr-message: 'Closing this PR because it has been stale for 7 additional days. Feel free to reopen when the work is ready.'
days-before-stale: 60
days-before-close: 7
only-prs: true
This workflow gives you two comment periods. First, the PR gets the stale label and a polite note. Then, if nothing changes after one more week, the action closes the PR automatically. You can adjust the schedule and the day windows to fit your team’s pace, but the important thing is to start with a gentle cadence and tighten it as the team gains confidence.
Tuning for Your Team’s Cadence
The biggest mistake in stale-pr automation is copying a template and forgetting to adapt it. A four-person startup shipping weekly will need different numbers than a large open-source project with maintainers who only triage once a month. In 2026, many teams have become more asynchronous and distributed, which means a thirty-day inactivity window may be too aggressive for collaborators in different time zones.
To avoid false positives, consider setting days-before-stale to 60 or 90. Then, use days-before-close to create a second window that is at least one full week, so real contributors have time to respond after they become aware of the warning. The stale action’s comment includes a unique identifier that it uses to avoid re-staling the same PR too quickly. If you want to allow unlimited re-staling on an occasional basis, you can set remove-stale-when-updated: true; by default, any comment or review removes the stale label and restarts the countdown.
Ignore your own bot comments
One subtle issue you may hit: a maintainer’s automated comment, such as a dependency bot update or a triage welcome message, can accidentally mark a PR as active. Use the action’s exempt-issue-labels and exempt-pr-labels to protect critical labels, and consider setting ignore-updates if comments alone should not reset the clock. This control matters more than ever in 2026, when bots often speak as much as humans in a repository.
Avoiding the Auto-Close Trap: Exemptions and Labels
Not every stale PR should be treated the same. Draft PRs, for instance, are often intentionally long-lived. If someone is working on a multi-week refactor and leaves a draft open as a progress indicator, closing it after sixty days could be counterproductive. Similarly, a PR that has been assigned to an upcoming milestone should never be closed just because no code commit landed in the last few weeks.
Below is an expanded set of options that protect high-value work:
exempt-pr-labels: 'wip,dependencies,do-not-merge'– lets you tag PRs that require extra time.only-labels: 'stale-candidate'– if you prefer to manually mark which PRs are eligible for auto-close.operations-per-run: 30– avoids hitting rate limits on larger repositories.stale-pr-label: 'inactive'– custom labels make your dashboard more readable.
Another practical tactic is to take advantage of the close-message to tell the contributor exactly how to proceed: edit the description, push a new commit, or just type “/still important.” That keeps the action deterministic while leaving a useful breadcrumb trail.
Handling Draft PRs and Long-Running Branches
Draft PRs deserve special attention. The default stale action configuration applies to drafts, but many teams have found it useful to set a longer stale window for draft PRs or to mark them with a draft label so they are excluded from the ordinary countdown. One way to achieve this is by using an initial workflow step to add a label to draft PRs, then referencing that label in the exempt-pr-labels field.
A second 2026-friendly refinement is to combine the stale action with a branch-aging report. Instead of closing every inactive PR forever, you can schedule a separate job that lists PRs that have been open for more than 90 days and sends a summary to maintainers. This gives the team a chance to archive or reassign work, rather than relying on the stale action to make the final decision. The two tools work together: the report gives visibility, and the stale action applies the policy.
If you want to also clean up remote branches after a stale PR closes, you might want to extend your automation with a branch-deletion workflow. That topic deserves its own guide, but the principle is the same—never let a closed PR leave behind a forgotten branch that confuses future contributors.
Monitoring and Adjusting the Stale Process
Automation is not a set-and-forget job. Once your stale action is live, look at what happens during the first few cycles. Did any PRs that were clearly important get the stale label? Did a contributor express confusion about why their PR was closed? If so, adjust the labels and timeframes. You can see every action the bot took in the Actions tab, and the comment history on each PR will show you exactly where the process needs a softer touch.
A useful monitoring habit is to add an automated daily metric that records the number of open PRs older than 30/60/90 days. You can post that number into a repository issue or a team chat webhook. This gives you a long-term trend, so you can tell whether the stale action is actually reducing backlog or just creating an illusion of cleanliness by closing PRs that your team still intended to review.
Conclusion
Automatically closing stale PRs with GitHub Actions is a classic quick fix, but the real value comes from tuning it to your team’s rhythm and protecting the exceptions. Use the stale action as a gentle nudge, not a blunt door-slam. With clear labels, reasonable timeframes, and regular monitoring, you can keep your pull request queue free of dead weight while making sure that no valuable contribution is quietly discarded.
