This tool is designed for educational purposes to understand GitHub automation, co-authoring workflows, and API integration. Use responsibly and in accordance with GitHub's Terms of Service.
| Default 1 PR | Bronze 10 PRs | Silver 24 PRs | Gold 48 PRs |
Automate co-authored PR creation and merging to unlock all achievement tiers!
A powerful Python automation framework that streamlines GitHub co-authored Pull Request workflows at scale. Perfect for developers who want to:
| 🎯 Use Case | 📊 Benefit |
|---|---|
| Co-authored Commits | Automatic Co-authored-by trailers on every commit |
| Badge Hunting | Earn GitHub Pair Extraordinaire badge efficiently |
| Batch Documentation | Update files across multiple branches with a collaborator |
| CI/CD Testing | Stress-test co-authoring automation pipelines |
| Learning & Research | Study GitHub automation patterns and workflows |
| 🌟 Features | 🏗️ Architecture | 📦 Installation | ⚙️ Configuration |
|---|---|---|---|
| 🚀 Usage | 🔧 Components | 🔐 Security | 🐛 Troubleshooting |
| 📈 Optimization | 🤝 Contributing | 📄 License | 📞 Support |
graph TB
Start([🚀 Start Automation]) --> Config[📋 Load Config & .env]
Config --> Mode{🔀 Choose Mode}
Mode -->|Sequential| Seq[📝 run.py]
Mode -->|Parallel| Par[⚡ Parallel Workers]
Seq --> Git[🔧 Git Operations]
Par --> Git
Git --> Branch[🌿 Create Branch]
Branch --> Commit[💾 Co-authored Commit]
Commit --> Push[📤 Push to Remote]
Push --> PR[🎯 Create PR]
PR --> AutoMerge{🤖 Auto-merge?}
AutoMerge -->|Yes| Merge[✅ Merge PR]
AutoMerge -->|No| Next{📊 More PRs?}
Merge --> Next
Next -->|Yes| Git
Next -->|No| Notify[📢 Send Notifications]
Notify --> End([🎉 Complete!])
┌─────────────────────────────────────────────────────────────────┐
│ 🤝 Pair Extraordinaire Automation │
│ Core Orchestration Layer │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
┌───────▼────────┐ ┌────────▼───────┐
│ 📝 Sequential │ │ ⚡ Parallel │
│ run.py │ │ src/async │
│ Reliable │ │ Fast Mode │
└───────┬────────┘ └────────┬───────┘
│ │
└──────────────┬───────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌────▼─────┐ ┌─────▼────┐ ┌──────▼──────┐
│ 🔧 src/ │ │ ⚙️ config│ │ 📄 templates│
│ Core Bot │ │ Settings │ │ Commit Msg │
└────┬─────┘ └─────┬────┘ └──────┬──────┘
│ │ │
└─────────────────────┼──────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ 🔑 Token │ │ 📢 Notify │ │ 📝 Logger │
│ Manager │ │ Webhooks │ │ System │
└───────────┘ └───────────┘ └───────────┘
| Component | Role | Features |
|---|---|---|
| run.py | 📝 Entry Point | Bootstraps automation, loads config |
| src/ | 🔧 Core Logic | Branch, commit, push, PR, merge |
| config/ | ⚙️ Settings | JSON/env-based configuration |
| templates/ | 📄 Templates | Commit messages & PR body templates |
| .env | 🔑 Secrets | GitHub tokens & co-author credentials |
✅ Python 3.8 or higher
✅ Git 2.0 or higher
✅ GitHub CLI (gh)
✅ Active GitHub account
✅ A collaborator account (for co-authoring)
✅ Write access to target repository
📥 Download the Code
# Clone via HTTPS
git clone https://github.com/itxashancode/Pair-Extraordinaire.git
# Or clone via SSH
git clone git@github.com:itxashancode/Pair-Extraordinaire.git
# Navigate to directory
cd Pair-Extraordinaire🐍 Python Packages
# Install all required packages
pip install -r requirements.txt
# Or use pip3 on some systems
pip3 install -r requirements.txt📦 What Gets Installed:
| Package | Purpose |
|---|---|
requests |
HTTP/API calls |
gitpython |
Git automation |
python-dotenv |
Load .env variables |
colorama |
Terminal colors |
aiohttp |
Async HTTP (parallel mode) |
🍎 macOS
brew install gh && gh --version🐧 Linux (Debian/Ubuntu)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
https://cli.github.com/packages stable main" | \
sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update && sudo apt install gh -y
gh --version🪟 Windows
winget install --id GitHub.cli
# Or: choco install gh
gh --version🔐 Login to GitHub
# Start authentication
gh auth login
# Follow the interactive prompts:
# 1. Choose: GitHub.com
# 2. Protocol: HTTPS
# 3. Authenticate Git: Yes
# 4. Method: Login with a web browser
# Verify authentication
gh auth statusExpected Output:
✓ Logged in to github.com as YourUsername
✓ Git operations for github.com configured to use https protocol.
✓ Token: *******************
Ready to configure? Proceed to ⚙️ Configuration
cp .env.example .env# GitHub credentials
GITHUB_TOKEN=your_personal_access_token_here
# Repository details
REPO_OWNER=your_github_username
REPO_NAME=your_repository_name
# Co-author details (required for Pair Extraordinaire)
CO_AUTHOR_NAME=Collaborator Name
CO_AUTHOR_EMAIL=collaborator@email.com
CO_AUTHOR_TOKEN=collaborator_github_token
# Notification webhooks (optional)
DISCORD_WEBHOOK=https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE
SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR_WEBHOOK_HERE📖 Configuration Reference:
| Variable | Required | Description |
|---|---|---|
GITHUB_TOKEN |
✅ Yes | Your GitHub Personal Access Token |
REPO_OWNER |
✅ Yes | GitHub username or org of the target repo |
REPO_NAME |
✅ Yes | Name of the target repository |
CO_AUTHOR_NAME |
✅ Yes | Display name of your co-author |
CO_AUTHOR_EMAIL |
✅ Yes | Email used by co-author on GitHub |
CO_AUTHOR_TOKEN |
Co-author token for mutual achievements | |
DISCORD_WEBHOOK |
❌ Optional | Discord channel webhook URL |
SLACK_WEBHOOK |
❌ Optional | Slack channel webhook URL |
{
"repo_path": ".",
"base_branch": "main",
"target_file": "README.md",
"pr_count": 50,
"delay_seconds": 15,
"auto_merge": true,
"dry_run": false,
"max_retries": 3
}📖 Configuration Reference:
| Parameter | Type | Default | Description |
|---|---|---|---|
repo_path |
string | "." |
Path to your Git repository |
base_branch |
string | "main" |
Target branch for PRs |
target_file |
string | "README.md" |
File modified in each commit |
pr_count |
integer | 50 |
Total co-authored PRs to create |
delay_seconds |
integer | 15 |
Wait time between PRs |
auto_merge |
boolean | true |
Automatically merge created PRs |
dry_run |
boolean | false |
Test mode — no actual changes |
max_retries |
integer | 3 |
Retry attempts on failure |
The templates/ directory contains message templates for commits and PR bodies. Every co-authored commit automatically appends the required trailer:
feat: automated update #{pr_number}
This is an automated co-authored commit.
Co-authored-by: {co_author_name} <{co_author_email}>
GitHub detects the Co-authored-by trailer and awards the badge to both users upon merge.
💬 Discord Webhook
- Discord Server → Settings → Integrations → Webhooks → New Webhook
- Name it, select a channel, copy the URL
- Paste into
.envasDISCORD_WEBHOOK
# Test your webhook
curl -X POST "YOUR_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"content": "Test from Pair Extraordinaire!"}'💼 Slack Webhook
- Visit api.slack.com/apps → New App → Incoming Webhooks
- Activate, add to workspace, select channel, copy URL
- Paste into
.envasSLACK_WEBHOOK
Next: 🚀 Usage Guide
# 1️⃣ Basic sequential run
python run.py
# 2️⃣ Test without creating PRs
python run.py --dry-run
# 3️⃣ Custom PR count
python run.py --count 50
# 4️⃣ Resume after interruption
python run.py # Auto-resumes from state filepython run.py [OPTIONS]⭐ Best For: Reliability, debugging, smaller PR counts
| Option | Description | Example |
|---|---|---|
--count N |
Override PR count | python run.py --count 50 |
--delay N |
Seconds between PRs | python run.py --delay 30 |
--dry-run |
Test mode (no commits) | python run.py --dry-run |
--no-merge |
Disable auto-merge | python run.py --no-merge |
--reset |
Start from PR #1 | python run.py --reset |
📊 Example Output:
🚀 Starting Pair Extraordinaire Automation from PR #1
⏱️ Delay: 15 seconds
🔹 Processing PR #1
├─ 🌿 Created branch: pair-auto-1-a1b2c3d4
├─ 💾 Committed with Co-authored-by trailer
├─ 📤 Pushed to remote
└─ ✅ PR #101 created successfully
✅ PR #101 merged — both users credited
⏱️ Waiting 15 seconds...
🔹 Processing PR #2
...
Example 1: Default Badge (1 PR)
Goal: Earn the default Pair Extraordinaire badge
# Test with dry run first
python run.py --count 2 --dry-run
# Run actual automation
python run.py --count 2 --delay 10Output:
🚀 Starting from PR #1
✅ PR #1-2 created and merged successfully
🎉 Default Pair Extraordinaire Badge unlocked!
Example 2: Bronze Badge (10 PRs)
Goal: Earn Bronze Pair Extraordinaire Badge
python run.py --count 12 --delay 15
# Expected time: ~3 minutesOutput:
🚀 Starting automation
📈 12/12 PRs complete
🎉 Bronze Pair Extraordinaire Badge unlocked!
⏱️ Completed in 3 minutes 1 second
Example 3: Gold Badge (48 PRs)
Goal: Earn Gold Pair Extraordinaire Badge
python run.py --count 50 --delay 10
# Expected time: ~8-10 minutesOutput:
🚀 Starting automation
📈 50/50 PRs complete ✓ 48 merged ✗ 2 retried
🎉🎉🎉 GOLD PAIR EXTRAORDINAIRE UNLOCKED! 🎉🎉🎉
⏱️ Completed in 8 minutes 45 seconds
Example 4: Resume After Interruption
Scenario: Process interrupted at PR #22
# Check current state
cat state.json
# Output: {"last_completed_pr": 22}
# Resume automatically
python run.py --count 50
# Continues from PR #23Example 5: Dry Run Testing
Goal: Test configuration without creating real PRs
python run.py --count 5 --dry-runOutput:
🔍 DRY RUN MODE - No actual commits
🔹 PR #1 Simulation
📝 Would create branch: pair-auto-1-abc123
📝 Would modify: README.md
📝 Would commit with Co-authored-by trailer
📝 Would push to: origin/pair-auto-1-abc123
📝 Would create PR: "Automated Co-authored PR #1"
✅ Simulation successful
✅ All 5 PRs simulated successfully
| 🎯 Tip 1 | Always start with --dry-run to validate your config |
| 🤝 Tip 2 | Make sure your co-author's email matches their GitHub account |
| 🔄 Tip 3 | State files allow safe resuming after any interruption |
| 📊 Tip 4 | Monitor rate limits with gh api rate_limit |
| ⚡ Tip 5 | Lower delay_seconds for faster runs (minimum ~10s recommended) |
🎯 Purpose: Bootstrap the automation, load configuration, and invoke the core pipeline
🔑 Responsibilities:
# run.py high-level flow
def main():
config = load_config() # Load config/ and .env
state = load_state() # Resume support
for pr_num in range(start, total):
branch = create_branch(pr_num)
commit_with_coauthor(branch) # Appends Co-authored-by
push(branch)
pr_id = create_pr(branch)
if config.auto_merge:
merge_pr(pr_id)
save_state(pr_num)
notify(pr_num)
sleep(config.delay_seconds)🎯 Purpose: Core bot logic — Git operations, GitHub API calls, PR management
✨ Key Features:
| Module | Role |
|---|---|
| Git Manager | Branch creation, co-authored commits, push |
| GitHub Tool | PR create, merge, rate-limit handling |
| Token Manager | Token rotation for higher throughput |
| State Manager | Persist progress for resumable runs |
🔑 Co-authored Commit Format:
git commit -m "Automated update #1
Co-authored-by: Collaborator Name <collaborator@email.com>"GitHub detects the Co-authored-by trailer and credits both users, triggering the Pair Extraordinaire badge upon merge.
✨ Features:
| ⏱️ Exponential Backoff | Retry delays: 5s → 10s → 20s |
| 🌐 Internet Check | Validates connection before operations |
| 🔁 Auto-Retry | Configurable attempts (default: 3) |
| ⏲️ Timeout Protection | 30-second command timeout |
🎯 Purpose: Centralize all settings, making the bot easy to tune without editing source code
Key responsibilities:
- Load and validate JSON settings
- Merge with environment variables from
.env - Provide defaults for all optional values
🎯 Purpose: Store reusable commit message and PR body templates
Example Template:
feat: automated update #{pr_number}
This is an automated co-authored commit for demonstration purposes.
Co-authored-by: {co_author_name} <{co_author_email}>
Templates make it easy to customize the message format without touching core code.
🎯 Purpose: Send real-time updates to Discord or Slack on automation progress
🎨 Notification Types:
| Level | Color | Use Case |
|---|---|---|
info |
🔵 Blue | Progress updates |
success |
🟢 Green | PR created/merged |
warning |
🟡 Yellow | Non-critical errors |
error |
🔴 Red | Critical failures |
📨 Example Notification:
✅ PR #25 created and merged successfully
Branch: pair-auto-25-xyz
Co-author: CollaboratorName
Speed: 3.8 PRs/min
| ✅ | Never commit .env to Git |
.env is in .gitignore by default |
| ✅ | Use environment variables | export GITHUB_TOKEN="ghp_xxx" |
| ✅ | Rotate tokens regularly | Every 30–90 days |
| ✅ | Use fine-grained tokens | Limit scope to specific repos |
| ✅ | Enable 2FA | Extra security layer on your account |
| ✅ | Monitor token usage | gh api rate_limit |
Step-by-Step Guide:
- GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click "Generate new token (classic)"
- Name:
Pair Extraordinaire Automation - Expiration:
90 days
- Name:
- Select required scopes: ✅
repo✅workflow - Click "Generate token" — copy immediately (shown only once!)
- Add to
.envasGITHUB_TOKEN
📊 Rate Limits:
| Authentication | Requests/Hour | Notes |
|---|---|---|
| Unauthenticated | 60 | Very limited |
| OAuth Token | 5,000 | Per token |
| Multiple Tokens (rotated) | 10,000+ | Automatic rotation |
📋 Problem: GitHub CLI is not installed or not in PATH.
✅ Solution:
# macOS
brew install gh
# Linux
sudo apt update && sudo apt install gh -y
# Windows
winget install --id GitHub.cli
# Verify
gh --version📋 Problem: GitHub CLI not authenticated or token expired.
✅ Solution:
gh auth login
# Or use token directly
echo "YOUR_TOKEN" | gh auth login --with-token
gh auth status📋 Problem: Too many API requests in a short time.
✅ Solutions:
# Check current rate limit
gh api rate_limit
# Increase delay in config
"delay_seconds": 60
# Add a second token to .env or config📋 Problem: Git push fails due to network or permissions.
✅ Solutions:
# Check connection
ping github.com
# Verify repository permissions
gh repo view --web
# Re-authenticate
gh auth refresh📋 Problem: Badge not awarded — co-author email doesn't match a GitHub account.
✅ Solution:
- Ensure
CO_AUTHOR_EMAILexactly matches the email in the co-author's GitHub account settings - The email must be associated with their GitHub profile (set as public or verified)
📋 Problem: File already contains identical content.
✅ Solutions:
# Reset state and start fresh
python run.py --reset
# Or change the target file in config
"target_file": "CONTRIBUTING.md"export LOG_LEVEL=DEBUG
python run.pyDebug Output Example:
DEBUG - Loaded config: pr_count=50, delay=15s
DEBUG - Co-author: CollaboratorName <email@example.com>
DEBUG - Executing: git push origin pair-auto-1-abc123
DEBUG - Command output: [SUCCESS]
DEBUG - PR created: #101
DEBUG - Merged PR #101 successfully
| Strategy | Impact | Difficulty | Implementation |
|---|---|---|---|
| Reduce delay | ⭐⭐⭐⭐ | 🟢 Easy | --delay 10 |
| Multiple tokens | ⭐⭐⭐⭐ | 🟢 Easy | Add tokens to config |
| Stable network | ⭐⭐⭐ | 🟡 Medium | Use wired connection |
| SSD storage | ⭐⭐ | 🔴 Hard | Run on SSD drive |
Test 1: Default Badge (2 PRs)
⏱️ Time: ~30 seconds
✅ Success: 2/2 PRs
📈 Speed: ~4 PRs/min
Test 2: Bronze Badge (10 PRs)
⏱️ Time: ~3 minutes
✅ Success: 10/10 PRs
📈 Speed: ~3.5 PRs/min
Test 3: Gold Badge (48 PRs)
⏱️ Time: ~10 minutes
✅ Success: 46/48 PRs
🔁 Retries: 3 operations
📈 Speed: ~4.8 PRs/min
# 1️⃣ Fork the repository
gh repo fork itxashancode/Pair-Extraordinaire
# 2️⃣ Clone your fork
git clone https://github.com/YOUR_USERNAME/Pair-Extraordinaire.git
cd Pair-Extraordinaire
# 3️⃣ Create feature branch
git checkout -b feature/amazing-feature
# 4️⃣ Make your changes
# 5️⃣ Commit with conventional commits
git commit -m "feat: add amazing feature"
# 6️⃣ Push to your fork
git push origin feature/amazing-feature
# 7️⃣ Create Pull Request
gh pr create --title "Add amazing feature" --body "Description..."# ✅ Good — Clear, documented, typed
def commit_with_coauthor(branch: str, pr_number: int) -> bool:
"""
Create a co-authored commit on the given branch.
Args:
branch: Branch name to commit on
pr_number: PR sequence number for the commit message
Returns:
True if successful, False otherwise
"""
try:
message = build_commit_message(pr_number)
return git.commit(branch, message)
except Exception as e:
logger.error(f"Commit failed: {e}")
return False
# ❌ Bad — Unclear, no types, no docs
def doCommit(b, n):
return git.commit(b, buildMsg(n))🎨 Formatting Tools:
# Auto-format with black
black *.py
# Check style with flake8
flake8 *.py
# Type checking with mypy
mypy *.pyFound a bug? Help us fix it!
Steps:
- Check existing issues
- Create a new issue with the template
- Include: OS & Python version, full error message, steps to reproduce, expected vs actual behavior
MIT License
Copyright (c) 2026 Pair Extraordinaire Automation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- GitHub — For excellent CLI and API tooling
- Python Community — For amazing open-source libraries
- Contributors — Everyone who improved this project
- You — For using Pair Extraordinaire Automation!
| 📖 Docs | 🐛 Issues |
|---|---|
| 💬 Discussions | 👤 Profile |
Co-author smarter. Earn your badge. Ship together.
Made with ❤️ by itxashancode · linktr.ee/itxashanvibes
⭐ Star this repo if you found it helpful!
