by2ndOpinion Team

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.

github-actionsci-cdautomationpull-requeststutorial

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:

  1. Triggers on every pull request open or update
  2. Extracts the PR diff from the GitHub API
  3. Sends it to 2ndOpinion for AI analysis
  4. Posts the results as a comment on the pull request
  5. 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 reject recommendations.
  • 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.