, 3 min read
The Day I Decided to Automate PR Reviews

Once upon a time, about a year ago, I was a newbie developer (still am but less), all bright-eyed and clueless. I submitted a PR for a human to review, and it passed, and I thought, 'Wow, I’m unstoppable'.

Well, I was not.

Fast forward to a few days ago, there I was, sitting in a RCA meeting, explaining why my code had broken something. Naturally, I did what any self-respecting developer would do: I blamed the PR reviewer (what else are RCA's for 🌝).

Now (after ignoring the fact that I was the one wrote the code), was it entirely their fault? Probably not. The mistake was subtle and hard to catch something even the most diligent reviewer could miss. That's when I thought, "Why trust humans when you can automate the job?" That’s when the idea hit me, build my own AI-powered PR reviewer that’s tireless, consistent, and immune to distractions like office snacks or Slack messages.

Turning Frustration into Code

My automated PR reviewer uses a combination of Git patching and Claude AI to analyze code changes and post comments directly on the PR.

First, the script extracts the patch of the changes you have made from the master branch.

def get_git_patch():
    try:
        default_branch = 'master'
        merge_base = subprocess.check_output(
            ["git", "merge-base", default_branch, "HEAD"],
            text=True
        ).strip()
        patch = subprocess.check_output(
            ["git", "diff", merge_base, "HEAD"],
            text=True
        )
        return patch
    except subprocess.CalledProcessError as e:
        print(f"Error while generating git patch: {e}")
        return None

The script then generates a dynamic prompt based on customizable rules that I can adjust as needed. This prompt guides Claude AI to review the PR, focusing on specific patterns or issues that matter most, like naming conventions or refactoring opportunities. Claude then provides feedback in a pre-defined format.

    FILE: <filepath>
    LINE: <line_number>
    SEVERITY: <severity>
    COMMENT: <your detailed comment>

The best part? I can update the rules at any time to make sure Claude keeps up with my evolving code standards and team quirks.

Claude then takes a look at the generated prompt and generates comments in a friendly, professional tone, well for now at least. But I have plans to make the developer question their life by adding roast mode. The response from, my friend, Claude is then formatted into a JSON object.

Then I use PyGithub to access the Github API using the Personal access token with repos access. The script needs to run on the branch you need to review, running on the branch allows it to get all the metadata like repository name and owner name, you just need to give the script the Pull request number and it will post the comments (with lines specified by the claude) on that PR.

def get_github_repo():
    try:
        remote_url = subprocess.check_output(
            ["git", "remote", "get-url", "origin"],
            text=True
        ).strip()
        
        if remote_url.startswith('git@github.com:'):
            repo_path = remote_url.split('git@github.com:')[1].replace('.git', '')
            return repo_path
        return None
    except subprocess.CalledProcessError as e:
        print(f"Error getting repository info: {e}")
        return None

def post_github_comments(comments: List[Dict], pr_number: int):
    if not GITHUB_TOKEN:
        print("Missing GitHub token. Skipping comment posting.")
        return False
    
    try:    
        github_repo = get_github_repo()
        g = Github(GITHUB_TOKEN)
        repo = g.get_repo(github_repo)
        pr = repo.get_pull(pr_number)
        
        for comment in comments:
            pr.create_review_comment(
                body=f"[{comment['severity'].upper()}] {comment['comment']}",
                commit=pr.get_commits().reversed[0],
                path=comment['file'],
                line=comment['line']
            )
        return True
    except Exception as e:
        print(f"Error posting to GitHub: {e}")
        return False

Pretty cool isn't it ? It will be cooler if I maybe create an Github App or an Github Action which will run on every PR, so that the painful but necessary job of PR review becomes easier and faster.