🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Build an AI Pull Request Description Generator with Claude API

Build a GitHub Action that automatically generates detailed PR descriptions using Claude API. Reads the git diff, generates summary, testing instructions, and risk assessment, then posts it back to the PR.

DevOpsBoys5 min read
Share:Tweet

Writing good PR descriptions takes time. Most engineers write a single line and move on. This GitHub Action uses Claude API to read your git diff and automatically generate a full PR description — summary, testing steps, risks, and all — every time a PR is opened.

This is different from PR review bots. Those tools review your code and leave comments. This tool generates the description the PR author should have written.

What It Builds

When a PR is opened:

  1. The Action reads the full git diff of the PR
  2. Sends it to Claude Haiku with a structured prompt
  3. Gets back a formatted PR description
  4. Posts it to the PR body via the GitHub API

The result is a PR description with: what changed, why it matters, how to test it, and what could break.

Prerequisites

  • GitHub repository
  • Anthropic API key (get one at console.anthropic.com)
  • Add ANTHROPIC_API_KEY to your repo's Settings → Secrets → Actions secrets

The Python Script

Save this as .github/scripts/generate_pr_description.py:

python
import os
import sys
import anthropic
import requests
 
def get_pr_diff(repo: str, pr_number: int, github_token: str) -> str:
    url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
    headers = {
        "Authorization": f"token {github_token}",
        "Accept": "application/vnd.github.v3.diff",
    }
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    # Truncate to 8000 chars to stay within token limits
    return response.text[:8000]
 
def generate_description(diff: str, pr_title: str) -> str:
    client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
 
    prompt = f"""You are a senior software engineer writing a pull request description.
 
PR Title: {pr_title}
 
Git Diff:
{diff}
 
Write a clear PR description in this exact format:
 
## Summary
[2-3 bullet points describing what changed and why]
 
## Changes Made
[Bullet list of specific files/components changed and what was done]
 
## How to Test
[Step-by-step instructions to verify this PR works correctly]
 
## Potential Risks
[Any areas that could break, edge cases to watch, or dependencies changed]
 
Be specific and technical. Use the actual file names and function names from the diff.
Do not add any commentary outside this format."""
 
    message = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )
    return message.content[0].text
 
def post_pr_description(
    repo: str,
    pr_number: int,
    github_token: str,
    description: str,
) -> None:
    url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
    headers = {
        "Authorization": f"token {github_token}",
        "Accept": "application/vnd.github.v3+json",
    }
    data = {"body": description}
    response = requests.patch(url, headers=headers, json=data)
    response.raise_for_status()
    print(f"PR description updated successfully.")
 
def main():
    repo = os.environ["GITHUB_REPOSITORY"]
    pr_number = int(os.environ["PR_NUMBER"])
    pr_title = os.environ["PR_TITLE"]
    github_token = os.environ["GITHUB_TOKEN"]
 
    print(f"Fetching diff for PR #{pr_number}...")
    diff = get_pr_diff(repo, pr_number, github_token)
 
    if not diff.strip():
        print("Empty diff. Skipping.")
        sys.exit(0)
 
    print("Generating PR description with Claude...")
    description = generate_description(diff, pr_title)
 
    print("Posting description to PR...")
    post_pr_description(repo, pr_number, github_token, description)
 
if __name__ == "__main__":
    main()

The GitHub Actions Workflow

Save this as .github/workflows/pr-description.yml:

yaml
name: Generate PR Description
 
on:
  pull_request:
    types: [opened]
 
jobs:
  generate-description:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: read
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
 
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
 
      - name: Install dependencies
        run: pip install anthropic requests
 
      - name: Generate and post PR description
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_TITLE: ${{ github.event.pull_request.title }}
        run: python .github/scripts/generate_pr_description.py

Note: GITHUB_TOKEN is automatically provided by GitHub Actions — you do not need to create this secret manually. Only ANTHROPIC_API_KEY needs to be added to your repo secrets.

Example Output

For a PR that adds a new Kubernetes health check endpoint, Claude generates:

## Summary
- Adds /healthz and /readyz endpoints to the FastAPI application for Kubernetes liveness and readiness probes
- Separates liveness (process is alive) from readiness (app is ready to serve traffic) to allow proper rolling deployments
- No database or external service dependencies in the liveness check to avoid cascading failures

## Changes Made
- `src/main.py`: Added two new route handlers at /healthz and /readyz
- `k8s/deployment.yaml`: Added livenessProbe and readinessProbe configuration pointing to new endpoints
- `tests/test_health.py`: Added test cases for both endpoints including failure scenarios

## How to Test
1. Run locally: uvicorn src.main:app --reload
2. Hit GET /healthz — expect {"status": "ok"} with 200
3. Hit GET /readyz — expect {"status": "ready", "db": "connected"} with 200
4. Apply to staging: kubectl apply -f k8s/deployment.yaml
5. Watch rollout: kubectl rollout status deployment/my-app

## Potential Risks
- The readiness probe checks database connectivity. If the database is slow on startup, pods may not become ready immediately and cause brief traffic disruption during deployments.
- Liveness probe failure interval is set to 3 failures before restart — confirm this matches your app's startup time.

That is a real, useful PR description generated in about 3 seconds.

Controlling When It Runs

The workflow only triggers on pull_request: opened. If you want it to also update on PR edits:

yaml
on:
  pull_request:
    types: [opened, synchronize]

With synchronize, the description updates every time new commits are pushed to the PR branch. This keeps the description accurate as the PR evolves.

Cost

Claude Haiku is cheap. A typical PR diff (8000 chars in, ~400 tokens out) costs around $0.0003 per PR. At 100 PRs per month, that is $0.03. Not worth worrying about.

If your diffs are large (monorepo PRs), the 8000-character truncation in the script prevents runaway costs. Adjust the truncation limit to your needs.

This is one of the simplest high-value automations you can add to any engineering team's workflow. The time it saves on writing descriptions pays back instantly.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments