Overview

AI code review in CI

1
Create a sandbox

A fresh VM per workflow run, with a five-minute timeout.

2
Clone the PR branch

Shallow clone of the head branch using the workflow's GITHUB_TOKEN.

3
Review the diff with an LLM

git diff origin/main...HEAD goes to the model with a reviewer prompt.

4
Run the test suite

npm install && npm test inside the sandbox, output streamed to the job log. A failing exit code fails the job.

5
Comment on the PR

The review is posted back through the GitHub API; the sandbox is killed.

The workflow#

name: AI Code Review
 
on:
  pull_request:
    types: [opened, synchronize]
 
permissions:
  pull-requests: write
 
jobs:
  ai-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install e2b openai
      - name: Run AI review
        env:
          E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
          PR_BRANCH: ${{ github.event.pull_request.head.ref }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          GITHUB_REPOSITORY: ${{ github.repository }}
        run: node review.mjs
name: AI Code Review
 
on:
  pull_request:
    types: [opened, synchronize]
 
permissions:
  pull-requests: write
 
jobs:
  ai-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install e2b openai
      - name: Run AI review
        env:
          E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
          PR_BRANCH: ${{ github.event.pull_request.head.ref }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          GITHUB_REPOSITORY: ${{ github.repository }}
        run: python review.py

The review script#

// review.mjs
import { Sandbox, CommandExitError } from 'e2b'
import OpenAI from 'openai'
 
const sandbox = await Sandbox.create({ timeoutMs: 300_000 })
 
await sandbox.commands.run(
  'git clone --depth 1 --branch "$PR_BRANCH" "https://x-access-token:${GITHUB_TOKEN}@github.com/${PR_REPO}.git" /home/user/repo',
  { envs: { PR_BRANCH: process.env.PR_BRANCH, PR_REPO: process.env.PR_REPO, GITHUB_TOKEN: process.env.GITHUB_TOKEN } }
)
 
const diff = await sandbox.commands.run('cd /home/user/repo && git diff origin/main...HEAD')
 
const review = (await new OpenAI().chat.completions.create({
  model: 'gpt-5.2-mini',
  messages: [
    { role: 'system', content: 'You are a senior code reviewer. Focus on bugs, security issues, and code quality.' },
    { role: 'user', content: `Review this diff:\n\n${diff.stdout}` },
  ],
})).choices[0].message.content
 
await sandbox.commands.run('cd /home/user/repo && npm install', { onStdout: console.log })
try {
  await sandbox.commands.run('cd /home/user/repo && npm test', { onStdout: console.log, onStderr: console.error })
} catch (err) {
  if (err instanceof CommandExitError) {
    console.error('Tests failed with exit code:', err.exitCode)
    await sandbox.kill()
    process.exit(1)
  }
  throw err
}
 
await fetch(`https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/issues/${process.env.PR_NUMBER}/comments`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ body: `## AI Code Review\n\n${review}` }),
})
 
await sandbox.kill()
# review.py
import os, sys, requests
from e2b import Sandbox, CommandExitException
from openai import OpenAI
 
sandbox = Sandbox.create(timeout=300)
 
sandbox.commands.run(
    'git clone --depth 1 --branch "$PR_BRANCH" "https://x-access-token:${GITHUB_TOKEN}@github.com/${PR_REPO}.git" /home/user/repo',
    envs={k: os.environ[k] for k in ("PR_BRANCH", "PR_REPO", "GITHUB_TOKEN")},
)
 
diff = sandbox.commands.run("cd /home/user/repo && git diff origin/main...HEAD")
 
review = OpenAI().chat.completions.create(
    model="gpt-5.2-mini",
    messages=[
        {"role": "system", "content": "You are a senior code reviewer. Focus on bugs, security issues, and code quality."},
        {"role": "user", "content": f"Review this diff:\n\n{diff.stdout}"},
    ],
).choices[0].message.content
 
sandbox.commands.run("cd /home/user/repo && npm install", on_stdout=print)
try:
    sandbox.commands.run("cd /home/user/repo && npm test", on_stdout=print, on_stderr=lambda d: print(d, file=sys.stderr))
except CommandExitException as err:
    print(f"Tests failed with exit code: {err.exit_code}", file=sys.stderr)
    sandbox.kill()
    sys.exit(1)
 
requests.post(
    f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/issues/{os.environ['PR_NUMBER']}/comments",
    headers={"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
    json={"body": f"## AI Code Review\n\n{review}"},
)
 
sandbox.kill()

The clone runs through commands.run because the sandbox git module is deprecated — see Git in a sandbox.

Next#

Was this page helpful?