Automated AI Code Review in GitHub Actions
Add AI code review to every pull request with a GitHub Actions workflow. Claude, Codex, and Gemini analyze diffs automatically on each push.
Every team knows the problem: pull requests pile up, reviewers get busy, and subtle bugs ship because nobody had time to look closely. Adding AI code review to GitHub Actions solves the coverage gap — every diff gets analyzed automatically, before any human looks at it.
This guide walks through setting up automated AI code review in a GitHub Actions workflow using 2ndOpinion, so Claude, Codex, and Gemini run on every pull request and post their findings as a PR comment.
What You Will Build
A GitHub Actions workflow that:
- Triggers on every pull request open or update
- Extracts the PR diff from the GitHub API
- Sends it to 2ndOpinion for AI analysis
- Posts the results as a comment on the pull request
- Optionally fails the check if the AI recommends rejecting the change
The whole setup takes about 10 minutes.
Prerequisites
- A 2ndOpinion API key — get one at get2ndopinion.dev/dashboard (free tier included)
- A GitHub repository with Actions enabled
- Basic familiarity with GitHub Actions syntax
Step 1: Store Your API Key as a Secret
Go to your repository's Settings > Secrets and variables > Actions and add a new repository secret:
- Name:
SECONDOPINION_API_KEY - Value: your
sk_2op_...API key
This keeps your key out of workflow files and prevents it from being exposed in logs.
Step 2: Create the Workflow File
Create .github/workflows/ai-review.yml in your repository:
name: AI Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
id: diff
run: |
git fetch origin ${{ github.base_ref }}
DIFF=$(git diff origin/${{ github.base_ref }}...HEAD)
echo "diff<<EOF" >> $GITHUB_OUTPUT
echo "$DIFF" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Run AI code review
id: review
run: |
RESPONSE=$(curl -s -X POST https://get2ndopinion.dev/api/gateway/opinion \
-H "Content-Type: application/json" \
-H "X-2ndOpinion-Key: ${{ secrets.SECONDOPINION_API_KEY }}" \
-d "$(jq -n \
--arg diff "${{ steps.diff.outputs.diff }}" \
--arg model "claude" \
'{diff: $diff, model: $model}')")
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
echo "$RESPONSE" | jq '.'
- name: Post review comment
uses: actions/github-script@v7
with:
script: |
const response = JSON.parse(`${{ steps.review.outputs.response }}`);
const rec = response.recommendation || 'unknown';
const risks = response.risks || [];
const recEmoji = { approve: '✅', review: '⚠️', reject: '❌' }[rec] || '🔍';
let body = `## AI Code Review ${recEmoji}\n\n`;
body += `**Recommendation:** ${rec.toUpperCase()}\n\n`;
if (risks.length > 0) {
body += `### Findings\n\n`;
for (const risk of risks) {
const sev = risk.severity || 'info';
body += `- **[${sev.toUpperCase()}]** ${risk.description}\n`;
if (risk.file) body += ` - File: \`${risk.file}\`\n`;
}
} else {
body += `No significant issues found.\n`;
}
body += `\n---\n*Powered by [2ndOpinion](https://get2ndopinion.dev) — AI-to-AI code review*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
This workflow runs on every PR, calls Claude via the 2ndOpinion API, and posts results as a comment. The jq call safely handles diff content that contains quotes and special characters.
Step 3: Use Consensus Review for Critical Branches
For branches like main or release/*, you may want all three models analyzing the code instead of just one. Swap the API endpoint and update the comment formatter:
- name: Run consensus review
id: review
run: |
RESPONSE=$(curl -s -X POST https://get2ndopinion.dev/api/gateway/consensus \
-H "Content-Type: application/json" \
-H "X-2ndOpinion-Key: ${{ secrets.SECONDOPINION_API_KEY }}" \
-d "$(jq -n --arg diff "${{ steps.diff.outputs.diff }}" '{diff: $diff}')")
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
Then update the comment script to render agreements and disagreements from the consensus response:
const consensus = response.consensus || {};
const agreements = consensus.agreements || [];
const disagreements = consensus.disagreements || [];
let body = `## Consensus AI Review ${recEmoji}\n\n`;
body += `**Recommendation:** ${rec.toUpperCase()}\n\n`;
if (agreements.length > 0) {
body += `### All Models Agree On\n\n`;
for (const a of agreements) {
body += `- **[${a.severity.toUpperCase()}]** ${a.description} `;
body += `*(${a.models.join(', ')})*\n`;
}
body += '\n';
}
if (disagreements.length > 0) {
body += `### Unique Findings\n\n`;
for (const d of disagreements) {
body += `- **[${d.severity.toUpperCase()}]** ${d.description} `;
body += `*(${d.model} only)*\n`;
}
}
Consensus review costs 3 credits per run. For high-traffic repositories, consider targeting it at PRs that touch security-sensitive paths using a path filter in your workflow trigger.
Step 4: Enforce on High-Risk Paths (Optional)
You can fail the check when the AI recommends rejecting a change. Add this step after the comment is posted:
- name: Enforce review decision
run: |
RECOMMENDATION=$(echo '${{ steps.review.outputs.response }}' | jq -r '.recommendation')
if [ "$RECOMMENDATION" = "reject" ]; then
echo "AI review returned REJECT. Failing the check."
exit 1
fi
This turns the AI review into a blocking gate. When the recommendation is reject, the workflow fails and the PR cannot be merged until the issues are addressed (or the check is manually overridden by a maintainer).
A lighter-weight approach: only block on rejections for specific file patterns. Add a paths filter to your workflow trigger:
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'src/auth/**'
- 'src/payments/**'
- 'src/api/**'
- 'migrations/**'
This runs the AI review and enforces it only for diffs that touch auth, payments, API routes, or database migrations — the areas where the cost of a missed bug is highest.
Step 5: Add the Security Audit for Dependency Changes
If your repository tracks package changes, add a separate workflow step that runs a security audit when package.json or requirements.txt changes:
- name: Security audit on dependency changes
if: |
contains(github.event.pull_request.changed_files, 'package.json') ||
contains(github.event.pull_request.changed_files, 'requirements.txt')
run: |
curl -s -X POST https://get2ndopinion.dev/api/gateway/security-audit \
-H "Content-Type: application/json" \
-H "X-2ndOpinion-Key: ${{ secrets.SECONDOPINION_API_KEY }}" \
-d "$(jq -n --arg diff "${{ steps.diff.outputs.diff }}" '{diff: $diff}')"
The security audit runs an OWASP-aligned analysis and specifically looks for vulnerable patterns in dependency imports, configuration changes, and API surface modifications.
Managing Credits in CI
Running AI code review on every PR costs credits. Here is how to stay within budget:
Free tier (5 credits/month) — suitable for personal projects with a few PRs per month.
Pro tier (100 credits/month at $10) — covers approximately 100 single-model reviews or 33 consensus reviews per month. Good for small teams.
Power tier (500 credits/month at $25) — covers around 500 single-model reviews. Suitable for active repositories with frequent PRs.
To check your remaining credits from the workflow, you can call the status endpoint before running the review and skip it if credits are exhausted rather than failing:
- name: Check available credits
id: credits
run: |
STATUS=$(curl -s https://get2ndopinion.dev/api/gateway/status \
-H "X-2ndOpinion-Key: ${{ secrets.SECONDOPINION_API_KEY }}")
CREDITS=$(echo "$STATUS" | jq '.credits.remaining // 0')
echo "remaining=$CREDITS" >> $GITHUB_OUTPUT
- name: Run AI review
if: steps.credits.outputs.remaining > 0
# ... rest of review step
A Complete Workflow for Most Teams
Here is what a balanced setup looks like for a team shipping regularly:
- All PRs: Single-model review with Claude (1 credit). Fast, catches the majority of issues.
- PRs to main/release: Consensus review with all three models (3 credits). Maximum coverage for code that ships.
- PRs touching auth/payments/migrations: Blocking enforcement. The check fails on
rejectrecommendations. - Everything else: Non-blocking. The comment posts but does not prevent merging.
This configuration runs efficiently on the Pro tier for most teams, with room to upgrade to Power if PR volume is high.
What the PR Comment Looks Like
After the workflow runs, reviewers see a structured comment on the PR before they read a single line of code:
## AI Code Review ⚠️
Recommendation: REVIEW
### Findings
- [HIGH] Potential race condition in updateUserBalance — check-then-act
pattern without transaction. File: `src/services/billing.ts`
- [MEDIUM] Error response leaks internal stack trace in production mode.
File: `src/middleware/errors.ts`
- [LOW] Missing input validation on `limit` query parameter.
File: `src/api/users.ts`
---
Powered by 2ndOpinion — AI-to-AI code review
Human reviewers still make the final call, but they start with a structured summary that points them directly at the areas that need attention. Reviews go faster, and the issues that slip through unreviewed PRs stop slipping through.
Get Started
The fastest way to try this before committing to a full workflow setup is the 2ndOpinion playground — paste a diff, run a review, and see exactly what the API returns. Then map that output to your comment template.
Full API documentation, including request/response schemas for every endpoint, is at get2ndopinion.dev/docs. Pricing and credit packs are at get2ndopinion.dev/pricing.
For teams already using the CLI locally, the same 2ndopinion review command works in CI — just set the SECONDOPINION_API_KEY environment variable and pipe git diff to it. No workflow file required.