From git diff to perfect PR description in 2 minutes using Claude
← Back
April 4, 2026Claude5 min read

From git diff to perfect PR description in 2 minutes using Claude

Published April 4, 20265 min read

Writing PR descriptions used to be the last thing I wanted to do after finishing a feature. I would stare at the diff, try to remember why I made each change, and end up with a description that said "Added user filtering" over 200 changed lines. Then I built a shell alias that pipes my git diff to Claude and gets a structured, human-readable PR description back in under two minutes. Here is the exact setup.

The core idea

git diff main...HEAD contains everything Claude needs to write a good PR description: what changed, which files, what was added or removed. Claude can infer intent from the code far better than most engineers can articulate it under time pressure.

The shell function

bash
# Add to ~/.zshrc or ~/.bashrc

pr-describe() {
  local base_branch="${1:-main}"
  local diff_output

  diff_output=$(git diff "${base_branch}...HEAD" 2>&1)

  if [ -z "$diff_output" ]; then
    echo "No diff found against ${base_branch}. Are you on a feature branch?"
    return 1
  fi

  echo "Generating PR description..."

  echo "$diff_output" | claude --print     --system "You are a senior engineer writing a GitHub pull request description.
Output a structured PR description with these sections:
## Summary
One paragraph explaining what this PR does and why.

## Changes
Bullet list of specific changes made.

## Testing
How the changes were tested or how a reviewer can verify them.

## Notes
Any migration steps, breaking changes, or important reviewer callouts.

Be specific and technical. Infer intent from the code changes.
Format for GitHub markdown."     "Write a PR description for this diff:" 
}

# Also useful: copy to clipboard
pr-describe-copy() {
  pr-describe "$@" | pbcopy
  echo "PR description copied to clipboard"
}

Using the Claude CLI

This uses the claude CLI. Install it with:

bash
npm install -g @anthropic-ai/claude-code

# Set your API key
export ANTHROPIC_API_KEY="your-key-here"

A version that uses the API directly

If you prefer not to use the CLI, here is a Python script you can call instead:

python
#!/usr/bin/env python3
# pr-describe.py — place in ~/bin/ and chmod +x

import sys
import subprocess
import anthropic

def get_diff(base_branch: str = "main") -> str:
    result = subprocess.run(
        ["git", "diff", f"{base_branch}...HEAD"],
        capture_output=True, text=True
    )
    return result.stdout

def generate_pr_description(diff: str) -> str:
    client = anthropic.Anthropic()
    
    # Truncate very large diffs to fit context window
    max_diff_chars = 50_000
    if len(diff) > max_diff_chars:
        diff = diff[:max_diff_chars] + "

[Diff truncated — showing first 50k chars]"
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1500,
        system="""You are a senior engineer writing a GitHub pull request description.
Write a clear, structured PR description with:
- ## Summary: What this PR does and why (1-2 paragraphs)
- ## Changes: Specific changes as bullet points
- ## Testing: How changes were tested
- ## Notes: Breaking changes, migrations, or reviewer callouts (if any)
Be specific and technical. Infer intent from the code.""",
        messages=[
            {
                "role": "user",
                "content": f"Write a PR description for this git diff:

{diff}"
            }
        ],
    )
    return response.content[0].text

if __name__ == "__main__":
    base = sys.argv[1] if len(sys.argv) > 1 else "main"
    diff = get_diff(base)
    
    if not diff.strip():
        print(f"No diff against {base}")
        sys.exit(1)
    
    print(generate_pr_description(diff))
bash
# In ~/.zshrc
pr-describe() {
  python3 ~/bin/pr-describe.py "${1:-main}"
}

pr-describe-copy() {
  pr-describe "$@" | pbcopy && echo "Copied to clipboard"
}

What great output looks like

For a recent auth refactor, the tool produced:

markdown
## Summary
Migrates JWT validation from HS256 (shared secret) to RS256 (public/private key pair).
This improves security posture by allowing downstream services to validate tokens
without access to the signing secret. Public key is distributed via environment variable.

## Changes
- Replace `jwt.sign(payload, SECRET)` with `jwt.sign(payload, PRIVATE_KEY, { algorithm: 'RS256' })`
- Add `RS256_PRIVATE_KEY` and `RS256_PUBLIC_KEY` to environment config
- Update all downstream service token validation to use public key
- Add `scripts/generate-keys.sh` for local key generation
- Update integration tests to use RSA key fixtures

## Testing
- All 47 existing auth tests pass with new implementation
- New tests added for: key rotation, malformed key errors, algorithm mismatch
- Tested locally with `scripts/generate-keys.sh` workflow

## Notes
⚠️ Breaking change: requires `RS256_PRIVATE_KEY` and `RS256_PUBLIC_KEY` env vars.
Update deployment configs before merging.
Old `JWT_SECRET` env var is no longer read.

The time savings add up

I open about 3-5 PRs per week. Writing a good description used to take 10-15 minutes when I was doing it properly. Now it takes 2 minutes to generate and 2 minutes to review and tweak. That is about 30 minutes a week I get back — time I spend on actual code review instead.

More importantly, the quality is higher. Claude catches things I would have glossed over in my own description, like noting that a change is a breaking migration or that tests were added for a specific edge case.

Share this
← All Posts5 min read