A Python tool that analyzes GitHub Pull Request review history and calculates the number of review cycles between a reviewer and the PR author.
A review cycle represents one complete iteration of the code review process:
┌─────────────────────────────────────────────────────────┐
│ Review Cycle │
├─────────────────────────────────────────────────────────┤
│ 1. Reviewer submits review (CHANGES_REQUESTED/COMMENT) │
│ 2. Author responds with commits (regular or force-push) │
│ 3. Reviewer submits next review (cycle completes) │
└─────────────────────────────────────────────────────────┘
- ✅ Standalone - No external Python dependencies, only uses standard library
- ✅ GitHub CLI Integration - Uses
ghCLI for authenticated GitHub API access - ✅ Flexible Output - Supports both Markdown (human-readable) and JSON (machine-readable)
- ✅ Detailed Tracking - Tracks reviews, commits, and force-pushes with timestamps
- ✅ Simple to Use - Just provide a PR URL and reviewer username
- Python 3.7+
- GitHub CLI (
gh) - Installation guide
# macOS
brew install gh
# Linux (Debian/Ubuntu)
sudo apt install gh
# Windows
winget install --id GitHub.cli
# After installation, authenticate
gh auth login# Download the script
curl -O https://raw.githubusercontent.com/EdDev/github-pr-review-cycles/main/pr_review_cycles.py
# Make it executable
chmod +x pr_review_cycles.py
# Run it
./pr_review_cycles.py --helpgit clone https://github.com/EdDev/github-pr-review-cycles.git
cd github-pr-review-cycles
python3 pr_review_cycles.py --helpAnalyze review cycles for a specific reviewer on a PR:
python3 pr_review_cycles.py \
--pr https://github.com/owner/repo/pull/123 \
--reviewer usernamepython3 pr_review_cycles.py --pr <PR_URL> --reviewer <USERNAME> [options]| Argument | Required | Description |
|---|---|---|
--pr |
✅ | Full GitHub PR URL (e.g., https://github.com/owner/repo/pull/123) |
--reviewer |
✅ | GitHub username of the reviewer to track |
--output |
❌ | Output format: markdown (default) or json |
--verbose, -v |
❌ | Enable verbose logging |
--version |
❌ | Show version and exit |
--help, -h |
❌ | Show help message |
python3 pr_review_cycles.py \
--pr https://github.com/RedHatQE/openshift-virtualization-tests-design-docs/pull/37 \
--reviewer EdDevOutput:
# Review Cycles for PR #37
**Repository:** RedHatQE/openshift-virtualization-tests-design-docs
**Reviewer:** EdDev
**Total Cycles:** 5
## Cycle Details
### Cycle 1
- **Review submitted:** 2026-03-03 15:23:13 UTC
- **Review state:** CHANGES_REQUESTED
- **Author committed:** 2026-03-09 10:23:30 UTC
- **Number of commits:** 2
- **Next review:** 2026-03-11 11:24:03 UTC
...Perfect for automation and data analysis:
python3 pr_review_cycles.py \
--pr https://github.com/owner/repo/pull/123 \
--reviewer EdDev \
--output json 2>/dev/nullOutput:
{
"pr_number": 123,
"repo": "owner/repo",
"reviewer": "EdDev",
"pr_author": "author",
"total_cycles": 3,
"cycles": [
{
"cycle": 1,
"review_at": "2024-01-15T10:30:00+00:00",
"review_state": "CHANGES_REQUESTED",
"commit_at": "2024-01-16T14:20:00+00:00",
"num_commits": 2,
"next_review_at": "2024-01-17T09:15:00+00:00",
"status": "Completed"
}
]
}python3 pr_review_cycles.py \
--pr https://github.com/owner/repo/pull/123 \
--reviewer EdDev \
--output json 2>/dev/null | jq '.total_cycles'python3 pr_review_cycles.py \
--pr https://github.com/owner/repo/pull/123 \
--reviewer EdDev \
--verboseCreate a script to analyze multiple PRs:
#!/bin/bash
PR_URLS=(
"https://github.com/owner/repo/pull/100"
"https://github.com/owner/repo/pull/101"
"https://github.com/owner/repo/pull/102"
)
REVIEWER="EdDev"
echo "PR,Reviewer,Cycles"
for pr_url in "${PR_URLS[@]}"; do
result=$(python3 pr_review_cycles.py --pr "$pr_url" --reviewer "$REVIEWER" --output json 2>/dev/null)
pr_num=$(echo "$result" | jq -r '.pr_number')
cycles=$(echo "$result" | jq -r '.total_cycles')
echo "$pr_num,$REVIEWER,$cycles"
donepython3 pr_review_cycles.py \
--pr https://github.com/owner/repo/pull/123 \
--reviewer EdDev \
--output json 2>/dev/null | \
jq -r '["PR", "Reviewer", "Total Cycles"],
([.pr_number, .reviewer, .total_cycles]) | @csv'-
Fetch Timeline - Uses GitHub GraphQL API (via
ghCLI) to fetch:- Pull Request reviews by specified reviewer
- Commits and force-pushes by PR author
-
Process Events - Implements a state machine:
INITIAL → AWAITING_CHANGES → AWAITING_REVIEW → (repeat) → FINAL -
Count Cycles - Tracks complete review-commit-review cycles
-
Generate Output - Formats as Markdown or JSON
Error: gh: Not Found (HTTP 404)
Solution: Authenticate with GitHub CLI:
gh auth loginError: Invalid GitHub PR URL format
Solution: Ensure you're using the complete PR URL:
# ✅ Correct
--pr https://github.com/owner/repo/pull/123
# ❌ Wrong
--pr https://github.com/owner/repo/issues/123 # Issue, not PR
--pr 123 # Just numberOutput: Total Cycles: 0
Possible causes:
- Reviewer username is case-sensitive - verify exact spelling
- Reviewer never reviewed this PR
- PR is very old and timeline data may be incomplete
- Summary header with repository, reviewer, and total cycles
- Detailed breakdown of each cycle with timestamps
- Review states (COMMENTED, CHANGES_REQUESTED, APPROVED)
- Commit counts and types (commit vs force-push)
{
"pr_number": int, // PR number
"repo": string, // "owner/repo"
"reviewer": string, // Reviewer username
"pr_author": string, // PR author username
"total_cycles": int, // Total number of cycles
"cycles": [
{
"cycle": int, // Cycle number (1-indexed)
"review_at": string, // ISO 8601 timestamp
"review_state": string, // COMMENTED | CHANGES_REQUESTED | APPROVED
"commit_at": string, // ISO 8601 timestamp or null
"num_commits": int, // Number of commits in this cycle
"next_review_at": string, // ISO 8601 timestamp or null
"status": string // "Completed" | "Merged/Approved"
}
]
}| Code | Meaning |
|---|---|
0 |
Success |
1 |
Error (invalid URL, API failure, etc.) |
130 |
Interrupted by user (Ctrl+C) |
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with GitHub CLI
- Uses GitHub GraphQL API for timeline data
Made with ❤️ for better code reviews