GitHub does not show you how long pull requests wait for review. Insights shows how many PRs were opened and merged, not how long anyone waited. Paid engineering-metrics tools do, but you do not need one to get the number this week.
This guide gives you a free script that measures PR review time for any repo you can read, explains what each number means, and lists the mistakes that make these numbers lie.
What to measure
- Time to first review: from “ready for review” to the first review by a human other than the author. This is the waiting time authors feel, and the number most worth fixing.
- Time to merge: from PR opened to merged.
- Cycle time: from the first commit to merged. See PR cycle time, explained for the stages inside it.
Report each as a median and a p90 (the time nine out of ten PRs beat). Averages are useless here: one PR forgotten for a month distorts them for weeks.
The script
pr_review_stats.py is about 150 lines of Python with no dependencies. It uses the GitHub CLI you probably already have, so it works on private repos without creating a token.
curl -O https://runmypr.com/tools/pr_review_stats.py
gh auth status # must be logged in
python3 pr_review_stats.py your-org/your-repo --prs 200
It reads your most recent merged PRs through the GitHub GraphQL API and handles the details that trip up most homemade versions:
- Drafts: the clock starts at the ready-for-review event, not at PR creation.
- Bots: reviews from bot accounts (Dependabot, Renovate, AI reviewers) are skipped, so they cannot make your pickup time look instant.
- Self-reviews: the author commenting on their own PR does not count.
- Unreviewed merges: PRs merged with no human review are counted and reported separately, not mixed into the median.
Example output
Here is the real output for the last 100 merged PRs in
facebook/react, run on Oct 6, 2026:
facebook/react: last 100 merged PRs, 99 with a human review
Time to first review median 5.4h p75 22.1h p90 5.2d n=99
Time to merge median 40.5h p75 8.1d p90 13.1d n=100
Cycle time median 37.0h p75 7.4d p90 13.4d n=100
Time to first review by PR size (lines added + deleted)
0-50 lines median 6.9h p75 18.4h p90 5.2d n=29
51-200 lines median 3.5h p75 24.8h p90 6.1d n=45
201-400 lines median 3.7h p75 12.6h p90 38.4h n=13
401-1000 lines median 19.7h p75 22.1h p90 8.6d n=7
1001+ lines median 44.4h p75 2.8d p90 2.9d n=5
1 PRs merged with no human review.
How to read it
Median vs p90
React’s median first review is 5.4 hours, which is excellent. Its p90 is 5.2 days. Both are true at once: most PRs are picked up fast, and a meaningful minority are forgotten. If your p90 is more than five times your median, you do not have a speed problem, you have an ownership problem: some PRs have no obvious reviewer.
Time to first review vs time to merge
React picks PRs up in hours but takes 40 hours to merge them. That gap is review rounds, CI and approval. If your gap is large, the first review is not the bottleneck; the rounds after it are.
The size table
This is usually the most persuasive part for a team. In React’s data, PRs up to 400 lines get a first review in a median of 4.5 hours; PRs over 1,000 lines wait 44. Small samples in the big buckets, but the pattern is the reason to read how big a pull request should be.
The quick version: one line of gh
If you just want to eyeball the last few PRs, this prints the hours from PR open to first non-bot review (it does not handle drafts, so use the script for real numbers):
gh pr list -R your-org/your-repo --state merged --limit 20 \
--json number,createdAt,author,reviews --jq '.[]
| .author.login as $me
| ([.reviews[] | select(.author.login != $me
and (.author.login | test("\\[bot\\]$|-app$|^copilot") | not))
| .submittedAt] | min) as $first
| [.number, (if $first then
(((($first | fromdateiso8601) - (.createdAt | fromdateiso8601))
/ 3600 * 10 | floor) / 10 | tostring) + "h"
else "no human review" end)]
| @tsv'
Mistakes that make the numbers lie
- Wall clock vs working hours. The script uses wall clock time, so a PR opened Friday evening “waits” all weekend. That is honest about what the author experiences, but compare week to week rather than against a business-hours SLA.
-
Too few PRs. Under about 30 reviewed PRs, the p90
is mostly noise. Use
--prs 200or more on a busy repo. - Mixing repos. A docs repo and a payments service have different norms. Measure them separately.
- Rewritten history. Commits recreated by a rebase or squash can carry dates later than the PR, so cycle time can come out shorter than time to merge. Trust time to merge if they disagree.
- Ranking people. Use these numbers for the team and the process, never for individual performance. The minute they are used to rank people, they stop being accurate.
What to do with the numbers
- Run it today and write down the median and p90 for time to first review.
- Pick one change: a review rotation, a size limit, or an automated first pass on every PR.
-
Run it again in two weeks with the same
--prsvalue and compare.
For what a good number looks like, see how long a PR review should take.