Build an AI GitHub PR Review Bot with Claude API (2026)
Build a GitHub Actions workflow that automatically reviews every pull request using Claude AI — catches bugs, security issues, and bad patterns before human review.
Code review is the bottleneck in most teams. An AI reviewer catches obvious bugs, security issues, and style violations instantly — freeing human reviewers for architecture discussions. Here's how to build one with Claude API and GitHub Actions.
What It Does
On every PR:
- GitHub Actions triggers
- Gets the diff (changed files)
- Sends diff to Claude API with a DevOps-focused review prompt
- Posts review comments directly on the PR
- Requests changes if critical issues found, approves if clean
Architecture
PR opened/updated
↓
GitHub Actions workflow
↓
Fetch PR diff via GitHub API
↓
Claude API (analyze diff)
↓
Post review comments via GitHub API
↓
Request Changes / Approve / Comment
Step 1: Get Claude API Key
- Go to console.anthropic.com
- Create API key
- Add to GitHub repo: Settings → Secrets →
ANTHROPIC_API_KEY
Step 2: The Review Script
Create .github/scripts/ai-review.js:
const https = require('https')
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
const REPO = process.env.GITHUB_REPOSITORY // owner/repo
const PR_NUMBER = process.env.PR_NUMBER
const BASE_URL = 'https://api.github.com'
// Fetch PR diff
async function getPRDiff() {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: `/repos/${REPO}/pulls/${PR_NUMBER}`,
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3.diff',
'User-Agent': 'AI-PR-Reviewer'
}
}
let data = ''
https.get(options, res => {
res.on('data', chunk => data += chunk)
res.on('end', () => resolve(data))
}).on('error', reject)
})
}
// Get PR files for targeted comments
async function getPRFiles() {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: `/repos/${REPO}/pulls/${PR_NUMBER}/files`,
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'AI-PR-Reviewer'
}
}
let data = ''
https.get(options, res => {
res.on('data', chunk => data += chunk)
res.on('end', () => resolve(JSON.parse(data)))
}).on('error', reject)
})
}
// Call Claude API
async function reviewWithClaude(diff, files) {
const fileList = files.map(f => `${f.status}: ${f.filename}`).join('\n')
const prompt = `You are a senior DevOps engineer reviewing a pull request. Analyze this diff and provide a thorough code review.
Changed files:
${fileList}
Diff:
\`\`\`diff
${diff.slice(0, 15000)}
\`\`\`
Review for:
1. **Security issues** — hardcoded secrets, insecure configs, exposed ports, missing auth
2. **Kubernetes best practices** — resource limits, health checks, security contexts, RBAC
3. **Docker best practices** — non-root user, image pinning, layer optimization
4. **CI/CD issues** — missing error handling, insecure workflows, missing tests
5. **Terraform issues** — missing state locking, no prevent_destroy on critical resources
6. **Performance issues** — inefficient queries, missing caching, no rate limits
7. **General bugs** — logic errors, typos in config keys, wrong port numbers
Format your response as JSON:
{
"verdict": "APPROVE" | "REQUEST_CHANGES" | "COMMENT",
"summary": "2-3 sentence overall assessment",
"issues": [
{
"severity": "critical" | "high" | "medium" | "low",
"file": "path/to/file",
"description": "Clear description of the issue",
"suggestion": "How to fix it"
}
],
"positives": ["What was done well"]
}`
return new Promise((resolve, reject) => {
const body = JSON.stringify({
model: 'claude-opus-4-6',
max_tokens: 2000,
messages: [{ role: 'user', content: prompt }]
})
const options = {
hostname: 'api.anthropic.com',
path: '/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Length': Buffer.byteLength(body)
}
}
let data = ''
const req = https.request(options, res => {
res.on('data', chunk => data += chunk)
res.on('end', () => {
const response = JSON.parse(data)
try {
const text = response.content[0].text
// Extract JSON from response
const jsonMatch = text.match(/\{[\s\S]*\}/)
resolve(JSON.parse(jsonMatch[0]))
} catch (e) {
resolve({ verdict: 'COMMENT', summary: response.content[0].text, issues: [], positives: [] })
}
})
})
req.on('error', reject)
req.write(body)
req.end()
})
}
// Post review to GitHub
async function postReview(review) {
const issuesList = review.issues.map(issue => {
const emoji = issue.severity === 'critical' ? '🚨' :
issue.severity === 'high' ? '⚠️' :
issue.severity === 'medium' ? '📝' : 'ℹ️'
return `${emoji} **${issue.severity.toUpperCase()}** — \`${issue.file}\`\n> ${issue.description}\n\n💡 **Fix:** ${issue.suggestion}`
}).join('\n\n---\n\n')
const positivesList = review.positives.map(p => `✅ ${p}`).join('\n')
const body = `## 🤖 AI Code Review
### Summary
${review.summary}
${review.issues.length > 0 ? `### Issues Found\n\n${issuesList}` : '### No Issues Found 🎉'}
${review.positives.length > 0 ? `### What's Good\n${positivesList}` : ''}
---
*Reviewed by Claude AI — [DevOpsBoys](https://devopsboys.com)*`
return new Promise((resolve, reject) => {
const bodyData = JSON.stringify({
body,
event: review.verdict, // APPROVE, REQUEST_CHANGES, or COMMENT
})
const options = {
hostname: 'api.github.com',
path: `/repos/${REPO}/pulls/${PR_NUMBER}/reviews`,
method: 'POST',
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'AI-PR-Reviewer',
'Content-Length': Buffer.byteLength(bodyData)
}
}
const req = https.request(options, res => {
let data = ''
res.on('data', chunk => data += chunk)
res.on('end', () => resolve(JSON.parse(data)))
})
req.on('error', reject)
req.write(bodyData)
req.end()
})
}
// Main
async function main() {
console.log(`Reviewing PR #${PR_NUMBER} in ${REPO}`)
const diff = await getPRDiff()
const files = await getPRFiles()
if (!diff || diff.length < 10) {
console.log('No diff found, skipping review')
return
}
console.log(`Diff size: ${diff.length} chars, ${files.length} files changed`)
const review = await reviewWithClaude(diff, files)
console.log('Review verdict:', review.verdict)
console.log('Issues found:', review.issues.length)
await postReview(review)
console.log('Review posted successfully')
// Fail CI if critical issues found
const criticalIssues = review.issues.filter(i => i.severity === 'critical')
if (criticalIssues.length > 0) {
console.error(`${criticalIssues.length} critical issues found`)
process.exit(1)
}
}
main().catch(err => {
console.error('Error:', err)
process.exit(1)
})Step 3: GitHub Actions Workflow
Create .github/workflows/ai-review.yml:
name: AI PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
# Optional: only review specific paths
# paths:
# - 'src/**'
# - 'Dockerfile'
# - '*.tf'
# - '.github/workflows/**'
jobs:
review:
runs-on: ubuntu-latest
# Only review PRs, not internal pushes
if: github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
pull-requests: write # needed to post review
steps:
- uses: actions/checkout@v4
- name: Run AI Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: node .github/scripts/ai-review.jsExample Review Output
When a PR contains a hardcoded secret in a Dockerfile:
## 🤖 AI Code Review
### Summary
This PR adds a new service deployment but contains a critical security issue —
a hardcoded API key in the Dockerfile. 2 medium issues also need attention.
### Issues Found
🚨 CRITICAL — `Dockerfile`
> Line 12: ENV API_KEY=sk-1234abcdsecret hardcodes a secret in the image layer.
> Anyone who pulls this image can extract the key with `docker history`.
💡 Fix: Remove the ENV line. Pass secrets at runtime via Kubernetes Secrets or
environment variables from your secret manager.
⚠️ HIGH — `k8s/deployment.yaml`
> No resource limits defined. Pod can consume unlimited CPU/memory,
> causing node-level resource starvation.
💡 Fix: Add resources.limits.cpu and resources.limits.memory to the container spec.
### What's Good
✅ Multi-stage build correctly separates build and runtime stages
✅ Non-root USER configured in final stage
Cost Estimate
- Claude claude-opus-4-6: ~$0.015 per 1K input tokens
- Average PR diff: ~3K tokens
- Cost per review: ~$0.05
- 100 PRs/month = ~$5/month
Use claude-haiku-4-5-20251001 for 10x cheaper reviews on smaller PRs: model: 'claude-haiku-4-5-20251001'
Customize the Prompt
The prompt is the most powerful lever. Tailor it to your stack:
// For infrastructure-focused repos
const prompt = `Review this Terraform/K8s diff for:
- Missing state locking
- No prevent_destroy on databases
- Public S3 buckets
- Security groups allowing 0.0.0.0/0
- Missing encryption at rest
...`
// For application repos
const prompt = `Review this Node.js/Python diff for:
- SQL injection vulnerabilities
- Missing input validation
- Exposed error messages with stack traces
- Hardcoded credentials
...`Resources
- Claude API Docs — API reference and model selection
- GitHub Actions Docs — workflow syntax reference
- KodeKloud CI/CD Path — hands-on GitHub Actions labs
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
Build a DevSecOps Pipeline from Scratch (2026 Project Walkthrough)
A complete end-to-end DevSecOps pipeline with SAST, container scanning, secrets detection, DAST, and supply chain security using open-source tools.
Build a DevSecOps Pipeline with Trivy, SonarQube, and OPA from Scratch (2026)
Step-by-step project walkthrough: add security scanning, code quality gates, and policy enforcement to a GitHub Actions pipeline. Real configs, production-ready.
Software Supply Chain Security: SBOM, SLSA, and Artifact Signing Complete Guide
A comprehensive guide to software supply chain security in 2026 — covering SBOMs, the SLSA framework, artifact signing with Cosign and Sigstore, and how to implement it all in your CI/CD pipeline.