The problem
Pull requests to other people's projects can stall in two ways. Some wait for the author (a merge conflict, a failing check, a reviewer's question) and the author does not notice. Others are closed by a stale bot after weeks without review, sometimes after they had already been approved. GitHub's notifications do not separate these cases, and a bot closure looks like any other closed pull request.
Stale bots are a recurring complaint (reaction counts as of 2026-10-20):
- actions/stale#719, "Default behavior can be disruptive for contributors of a repository", open, 17 reactions.
- actions/stale#861, "Issues should be kept active if reactions or backlinks", open, 17 reactions.
- Requests to remove the stale bot in nim-lang/Nim#15159 (30), arvidn/libtorrent#6797 (19), prusa3d/Prusa-Firmware-Buddy#5133 (10) and signalapp/Signal-iOS#5931 (9).
Dashboards such as gh-dash show open pull requests. No existing tool was found that finds bot-closed pull requests or says who has to act on each open one.
How it works
pr-rescue uses the login of the GitHub CLI and a few GraphQL search queries to fetch the user's open, closed and merged pull requests in repositories they do not own. It sorts them into three groups:
- Waiting on you: a merge conflict, requested changes, failing checks, a
draft, or a comment from a person since your last commit that has no answer.
This group also holds pull requests a bot closed without merging, marked
APPROVEDwhen they had an approval, with a note whether the branch still exists, which decides whether a reopen can work. - Waiting on maintainers: approved but not merged, no review yet, or cancelled checks that need a rerun a fork cannot trigger, each with the days idle.
- Already handled: bot-closed pull requests that a later pull request of yours references, for example with "Replaces #123".
Pull requests closed by a person, or by the author, are left out. Checks are
read one by one. A status that only says a deployment preview needs approval
(Vercel's "Authorization required to deploy" on fork pull requests) does not
count as a failure. A gate job such as enforce-all-checks that fails only
because another job was cancelled is not reported separately. Failing checks
are named in the output.
This is the example output from the README, with illustrative repository names:
$ pr-rescue
Waiting on you (3)
acme/router#5059 checks failing: test (3.11) [open, 78d idle]
Add retry budget to the proxy
https://github.com/acme/router/pull/5059
acme/sdk#1472 closed by github-actions, branch still exists: reopen or resubmit [closed, 68d idle]
fix(cli): pass keyword arguments to the start function
https://github.com/acme/sdk/pull/1472
acme/storage#9066 merge conflict [open, 34d idle]
feat: per-test repeat pass rate threshold
https://github.com/acme/storage/pull/9066
Waiting on maintainers (2)
acme/gateway#1638 approved, not merged [open, 0d idle]
...
Already handled (1)
acme/serving#5636 closed by github-actions, resubmitted as #6421 [closed, 60d idle]
--json prints the list with the fields waiting_on, reason, approved and
idle_days.
Testing
The tests build pull request records in the shape the GraphQL query returns and
pass them to the classifier. They cover bot-closed pull requests with and
without a branch or a successor, closures by a person, merged pull requests,
the open states and comments from a person, a bot and the author. A fake
GitHub client returns result pages for the three searches, so a test can check
that resubmissions are linked, the results are sorted and every query excludes
the user's own repositories. Four tests cover the check rules: the Vercel
status, a cancelled job with a failing gate job, a gate job that fails alone,
and a real failure next to a cancellation. The tests make no network calls. CI
runs them on Python 3.10 to 3.13, runs ruff and calls --version and --help.
Limitations
- Bot detection uses the account type and a short list of well-known bot logins. A person who closes pull requests from a personal account counts as a person.
- A reopen can still fail when the base branch moved on or the head branch was force-pushed. A new pull request that mentions the old one is then listed under "Already handled".
- A check that fails because of an external timeout is reported as failing. The tool cannot distinguish it from a failure caused by the change.
- GitHub search returns at most 1000 results per query.
The code and installation instructions are at github.com/Jwrede/pr-rescue.