Build an AI Code Review Bot with GitHub Actions and Claude API (2026)
Automate code reviews on every PR using Claude AI via GitHub Actions. The bot reviews Dockerfile security, Terraform changes, and general code quality โ and posts inline comments.
Build a GitHub Actions bot that automatically reviews PRs using Claude API โ comments on security issues in Dockerfiles, risky Terraform changes, and general code quality. No external service needed.
What We're Building
Every PR triggers a workflow that:
- Gets the diff from the PR
- Sends it to Claude API for review
- Posts review comments inline on the PR via GitHub API
Step 1: Set Up API Keys as Secrets
# In your GitHub repo: Settings โ Secrets โ Actions
# Add:
ANTHROPIC_API_KEY = sk-ant-...Step 2: Create the Review Script
# .github/scripts/ai_review.py
import os
import sys
import json
import anthropic
import requests
def get_pr_diff():
"""Get the diff for this PR from GitHub API."""
repo = os.environ["GITHUB_REPOSITORY"]
pr_number = os.environ["PR_NUMBER"]
token = os.environ["GITHUB_TOKEN"]
url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files"
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
def review_with_claude(diff_content: str, filename: str) -> str:
"""Send diff to Claude for review."""
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
system_prompt = """You are an expert DevOps engineer reviewing code changes.
Focus on:
- Security vulnerabilities (hardcoded secrets, dangerous permissions, exposed ports)
- Dockerfile best practices (non-root user, multi-stage builds, specific image versions)
- Terraform risks (resource deletions, IAM over-permissions, state issues)
- Kubernetes security (privileged pods, missing resource limits, RBAC issues)
- CI/CD pipeline security (secret exposure, injection risks)
Be concise. Only comment on real issues โ don't be pedantic about style.
Format your response as JSON:
{
"issues": [
{
"severity": "critical|warning|info",
"line": <line number in the diff if applicable, otherwise null>,
"message": "short description of the issue",
"suggestion": "how to fix it"
}
],
"summary": "one sentence overall assessment"
}
If no issues found, return {"issues": [], "summary": "LGTM - no issues found"}"""
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=system_prompt,
messages=[
{
"role": "user",
"content": f"Review this change to `{filename}`:\n\n```diff\n{diff_content}\n```"
}
]
)
return message.content[0].text
def post_pr_comment(body: str):
"""Post a comment on the PR."""
repo = os.environ["GITHUB_REPOSITORY"]
pr_number = os.environ["PR_NUMBER"]
token = os.environ["GITHUB_TOKEN"]
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",
}
data = {"body": body}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
def main():
files = get_pr_diff()
# Only review relevant file types
review_extensions = {
".tf", ".tfvars", # Terraform
"Dockerfile", # Docker
".yml", ".yaml", # YAML (K8s, CI/CD)
".py", ".go", ".js", # Code files
".sh", # Shell scripts
}
all_issues = []
reviewed_files = []
for file in files:
filename = file["filename"]
patch = file.get("patch", "")
# Check if we should review this file
should_review = any(
filename.endswith(ext) or filename == ext.lstrip(".")
for ext in review_extensions
) or "Dockerfile" in filename
if not should_review or not patch or len(patch) > 4000:
continue
print(f"Reviewing {filename}...")
try:
result_json = review_with_claude(patch, filename)
result = json.loads(result_json)
if result["issues"]:
reviewed_files.append(filename)
for issue in result["issues"]:
all_issues.append({
"file": filename,
**issue
})
except Exception as e:
print(f"Error reviewing {filename}: {e}")
continue
# Build the comment
if not all_issues:
comment = "## ๐ค AI Code Review\n\nโ
**No issues found** โ LGTM!\n\n*Reviewed by Claude AI*"
else:
critical = [i for i in all_issues if i["severity"] == "critical"]
warnings = [i for i in all_issues if i["severity"] == "warning"]
infos = [i for i in all_issues if i["severity"] == "info"]
lines = ["## ๐ค AI Code Review\n"]
if critical:
lines.append(f"### ๐จ Critical Issues ({len(critical)})\n")
for issue in critical:
lines.append(f"**`{issue['file']}`**: {issue['message']}")
lines.append(f"> ๐ก {issue['suggestion']}\n")
if warnings:
lines.append(f"### โ ๏ธ Warnings ({len(warnings)})\n")
for issue in warnings:
lines.append(f"**`{issue['file']}`**: {issue['message']}")
lines.append(f"> ๐ก {issue['suggestion']}\n")
if infos:
lines.append(f"### โน๏ธ Suggestions ({len(infos)})\n")
for issue in infos:
lines.append(f"**`{issue['file']}`**: {issue['message']}\n")
lines.append(f"\n*Reviewed by Claude AI ยท {len(reviewed_files)} files checked*")
comment = "\n".join(lines)
post_pr_comment(comment)
# Fail the action if there are critical issues
if any(i["severity"] == "critical" for i in all_issues):
print("Critical issues found โ failing the check")
sys.exit(1)
if __name__ == "__main__":
main()Step 3: GitHub Actions Workflow
# .github/workflows/ai-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write # needed to post comments
contents: read
steps:
- 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: Run AI review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: python .github/scripts/ai_review.pyStep 4: Try It Out
Open a PR with a file like this:
# Bad Dockerfile (should trigger review)
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python3
COPY . .
ENV AWS_SECRET_KEY=mysecretkey123 # hardcoded secret!
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]The bot should comment with something like:
๐จ Critical Issues
Dockerfile: Hardcoded AWS secret key in ENV instruction๐ก Remove the secret from the Dockerfile. Use ARG for build-time secrets or inject via environment variables at runtime. Never commit credentials to source control.
Cost Estimate
Claude Sonnet API pricing (as of 2026):
- ~$3 per million input tokens, ~$15 per million output tokens
- Average PR diff review: ~500 input tokens, ~300 output tokens
- Cost per PR review: ~$0.002 (less than half a cent)
For a team with 100 PRs/month: ~$0.20/month. Essentially free.
Enhancements
Skip review for bots:
if: github.actor != 'dependabot[bot]' && !contains(github.actor, 'bot')Cache reviews so re-runs don't re-review unchanged files:
# Check if file changed since last review by comparing SHA
file_sha = file["sha"]Add specific rules for your stack:
# In the system prompt, add company-specific rules
"""Additional company rules:
- All Kubernetes deployments must have resource limits
- All Terraform resources must have a 'team' tag
- No direct secrets in GitHub Actions env vars (use secrets context)"""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
AI Agents Will Replace DevOps Bash Scripts โ And That's a Good Thing
The future of DevOps automation is not more bash scripts. AI agents that can reason, adapt, and self-correct are quietly making traditional scripting obsolete. Here is what that means for DevOps engineers in 2026 and beyond.
AWS CodePipeline vs GitHub Actions โ Which CI/CD Tool to Use? (2026)
AWS CodePipeline and GitHub Actions both automate deployments. But they have very different strengths. Here's an honest comparison with real examples.
Build a Complete CI/CD Pipeline with GitHub Actions + ArgoCD + EKS (2026)
A full project walkthrough โ from a simple app to a production-grade GitOps pipeline with automated builds, image scanning, and deployments to AWS EKS using ArgoCD.