Skip to content
Learn Agentic AI
Learn Agentic AI13 min read14 views

Building a Claude Code Review Agent: Automated PR Analysis and Suggestions

Build a code review agent that parses GitHub PR diffs, analyzes code changes with Claude, generates actionable suggestions, and posts review comments via the GitHub API.

Why Automate Code Reviews

Code reviews are critical for code quality, but they create bottlenecks. Reviewers miss subtle bugs when fatigued, junior developers wait days for feedback, and style issues consume review time that could be spent on logic and architecture. A Claude-powered code review agent handles the repetitive parts — style enforcement, bug pattern detection, security scanning, and documentation checks — letting human reviewers focus on design decisions and business logic.

The agent we will build fetches PR diffs from GitHub, analyzes each changed file with Claude, generates specific suggestions with line-level precision, and posts review comments back to the PR.

Fetching PR Diffs from GitHub

Use the GitHub API to get the pull request diff and file changes:

flowchart TD
    START["Building a Claude Code Review Agent: Automated PR…"] --> A
    A["Why Automate Code Reviews"]
    A --> B
    B["Fetching PR Diffs from GitHub"]
    B --> C
    C["Analyzing Code Changes with Claude"]
    C --> D
    D["The Complete Review Pipeline"]
    D --> E
    E["Posting Review Comments to GitHub"]
    E --> F
    F["Running as a GitHub Action"]
    F --> G
    G["FAQ"]
    G --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
import requests
import os

GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]

def get_pr_diff(owner: str, repo: str, pr_number: int) -> dict:
    """Fetch PR details and file diffs."""
    headers = {
        "Authorization": f"token {GITHUB_TOKEN}",
        "Accept": "application/vnd.github.v3+json",
    }

    # Get PR metadata
    pr_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
    pr_data = requests.get(pr_url, headers=headers).json()

    # Get changed files with patches
    files_url = f"{pr_url}/files"
    files = requests.get(files_url, headers=headers).json()

    return {
        "title": pr_data["title"],
        "description": pr_data.get("body", ""),
        "base_branch": pr_data["base"]["ref"],
        "head_branch": pr_data["head"]["ref"],
        "files": [
            {
                "filename": f["filename"],
                "status": f["status"],  # added, modified, removed
                "patch": f.get("patch", ""),
                "additions": f["additions"],
                "deletions": f["deletions"],
            }
            for f in files
            if f.get("patch")  # Skip binary files
        ]
    }

Analyzing Code Changes with Claude

Send each file's diff to Claude with structured instructions for what to look for:

import anthropic
import json

client = anthropic.Anthropic()

review_tool = {
    "name": "submit_review_comments",
    "description": "Submit code review comments for specific lines in the diff",
    "input_schema": {
        "type": "object",
        "properties": {
            "comments": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "file": {"type": "string", "description": "Filename"},
                        "line": {"type": "integer", "description": "Line number in the diff"},
                        "severity": {
                            "type": "string",
                            "enum": ["critical", "warning", "suggestion", "nitpick"]
                        },
                        "category": {
                            "type": "string",
                            "enum": ["bug", "security", "performance", "style", "logic", "documentation"]
                        },
                        "comment": {"type": "string", "description": "The review comment with explanation"},
                        "suggested_fix": {"type": "string", "description": "Suggested code replacement if applicable"}
                    },
                    "required": ["file", "line", "severity", "category", "comment"]
                }
            },
            "summary": {"type": "string", "description": "Overall review summary"}
        },
        "required": ["comments", "summary"]
    }
}

def review_file(filename: str, patch: str, pr_context: str) -> dict:
    """Review a single file's changes."""
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        tools=[review_tool],
        tool_choice={"type": "tool", "name": "submit_review_comments"},
        system="""You are an expert code reviewer. Analyze the diff and provide specific,
actionable feedback. Focus on:
1. Bugs and logic errors (highest priority)
2. Security vulnerabilities (SQL injection, XSS, auth bypasses)
3. Performance issues (N+1 queries, missing indexes, memory leaks)
4. Error handling gaps (uncaught exceptions, missing validation)
5. Code style and readability issues (lowest priority)

Be specific — reference exact line numbers and explain WHY something is an issue,
not just WHAT the issue is. Only comment on changed lines (lines starting with +).
If the code looks good, say so with an empty comments array.""",
        messages=[{
            "role": "user",
            "content": f"PR Context: {pr_context}\n\nFile: {filename}\n\nDiff:\n{patch}"
        }]
    )

    for block in response.content:
        if block.type == "tool_use":
            return block.input
    return {"comments": [], "summary": "No issues found"}

The Complete Review Pipeline

Orchestrate the review across all changed files:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

def review_pull_request(owner: str, repo: str, pr_number: int) -> dict:
    """Run a complete code review on a pull request."""
    pr_data = get_pr_diff(owner, repo, pr_number)

    pr_context = f"PR Title: {pr_data['title']}\nDescription: {pr_data['description']}"

    all_comments = []
    file_summaries = []

    for file_info in pr_data["files"]:
        if file_info["status"] == "removed":
            continue  # Skip deleted files

        print(f"Reviewing {file_info['filename']}...")
        review = review_file(
            file_info["filename"],
            file_info["patch"],
            pr_context,
        )

        for comment in review.get("comments", []):
            comment["file"] = file_info["filename"]
            all_comments.append(comment)

        file_summaries.append({
            "file": file_info["filename"],
            "summary": review.get("summary", ""),
        })

    # Sort by severity
    severity_order = {"critical": 0, "warning": 1, "suggestion": 2, "nitpick": 3}
    all_comments.sort(key=lambda c: severity_order.get(c["severity"], 99))

    return {
        "pr_number": pr_number,
        "total_comments": len(all_comments),
        "critical_count": sum(1 for c in all_comments if c["severity"] == "critical"),
        "comments": all_comments,
        "file_summaries": file_summaries,
    }

Posting Review Comments to GitHub

Post the agent's findings as a GitHub PR review:

def post_review_to_github(owner: str, repo: str, pr_number: int,
                           review_data: dict, commit_sha: str):
    """Post review comments to GitHub PR."""
    headers = {
        "Authorization": f"token {GITHUB_TOKEN}",
        "Accept": "application/vnd.github.v3+json",
    }

    # Build GitHub review comments
    gh_comments = []
    for comment in review_data["comments"]:
        severity_emoji = {
            "critical": "[CRITICAL]",
            "warning": "[WARNING]",
            "suggestion": "[SUGGESTION]",
            "nitpick": "[NITPICK]",
        }
        prefix = severity_emoji.get(comment["severity"], "")
        body = f"**{prefix} {comment['category'].upper()}**\n\n{comment['comment']}"

        if comment.get("suggested_fix"):
            body += f"\n\n**Suggested fix:**\n```suggestion\n{comment['suggested_fix']}\n```"

        gh_comments.append({
            "path": comment["file"],
            "line": comment["line"],
            "body": body,
        })

    # Determine review action based on findings
    if review_data["critical_count"] > 0:
        event = "REQUEST_CHANGES"
    elif review_data["total_comments"] > 0:
        event = "COMMENT"
    else:
        event = "APPROVE"

    # Create the review
    review_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/reviews"
    review_body = {
        "commit_id": commit_sha,
        "body": generate_review_summary(review_data),
        "event": event,
        "comments": gh_comments,
    }

    response = requests.post(review_url, headers=headers, json=review_body)
    return response.json()

def generate_review_summary(review_data: dict) -> str:
    critical = review_data["critical_count"]
    total = review_data["total_comments"]
    summary = f"## Automated Code Review\n\n"
    summary += f"Found **{total}** issues ({critical} critical).\n\n"

    for fs in review_data["file_summaries"]:
        summary += f"- **{fs['file']}**: {fs['summary']}\n"

    return summary

Running as a GitHub Action

Trigger the review agent on every PR:

# .github/workflows/code-review.yml
name: AI Code Review
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install anthropic requests
      - run: python scripts/review_pr.py
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPO_OWNER: ${{ github.repository_owner }}
          REPO_NAME: ${{ github.event.repository.name }}

FAQ

How do I prevent the agent from being too noisy with nitpick comments?

Add a severity filter in your review pipeline — only post comments with severity "critical" or "warning" by default. Store nitpicks separately for developers who want detailed feedback. You can also instruct Claude to limit total comments to the 10 most important findings, forcing it to prioritize.

Can the agent understand context beyond the diff?

Yes. You can fetch the full file content (not just the diff) from GitHub and include it in the prompt. This helps Claude understand the broader code context — what functions the changed code calls, what patterns the rest of the file follows, and whether the changes are consistent with existing style.

How much does it cost to review a typical PR?

A PR with 500 lines changed across 10 files typically uses 30,000-50,000 input tokens and 3,000-5,000 output tokens per file review. With Claude Sonnet, this costs roughly $0.50-$1.50 per PR. Using prompt caching for the system prompt reduces this by 20-30% for subsequent reviews. Batch processing non-urgent reviews saves an additional 50%.


#Claude #CodeReview #GitHub #PullRequests #Python #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Interview Prep

7 AI Coding Interview Questions From Anthropic, Meta & OpenAI (2026 Edition)

Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.

Learn Agentic AI

Building a Multi-Agent Data Pipeline: Ingestion, Transformation, and Analysis Agents

Build a three-agent data pipeline with ingestion, transformation, and analysis agents that process data from APIs, CSVs, and databases using Python.

Learn Agentic AI

How to Build an AI Coding Assistant with Claude and MCP: Step-by-Step Guide

Build a powerful AI coding assistant that reads files, runs tests, and fixes bugs using the Claude API and Model Context Protocol servers in TypeScript.

Learn Agentic AI

Building Your First MCP Server: Connect AI Agents to Any External Tool

Step-by-step tutorial on building an MCP server in TypeScript, registering tools and resources, handling requests, and connecting to Claude and other LLM clients.

Learn Agentic AI

Computer Use Agents 2026: How Claude, GPT-5.4, and Gemini Navigate Desktop Applications

Comparison of computer use capabilities across Claude, GPT-5.4, and Gemini including accuracy benchmarks, speed tests, supported applications, and real-world limitations.

Learn Agentic AI

Building a Research Agent with Web Search and Report Generation: Complete Tutorial

Build a research agent that searches the web, extracts and synthesizes data, and generates formatted reports using OpenAI Agents SDK and web search tools.