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.mjsname: 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.pyThe 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.