Skip to content
Runtime

How to build an AI code review bot that runs the code it reviews

Check out the pull request in a sandbox. Let the model run its tests and linters there. Then post the review from your service.

A reviewer that can run the code catches what reading misses. On Runtime, its machine costs $0.03125 per hour while the model thinks, for 2 vCPUs and 4 GiB. Runtime bills measured CPU, not reserved cores. That made it 42% to 88% cheaper than fourteen other sandbox providers on a job that mostly waits. The rates were checked on 25 October 2026 (the comparison).

Reading a diff versus running it

A model that only reads a diff guesses. It cannot tell whether a new test passes, whether a changed function still type-checks, or whether the "harmless refactor" breaks an import two files away. Given a machine, the reviewer can find out. It can then report the command and its output as evidence. That turns "this might fail" into "npm test fails on line 41 with this error." A person can act on that comment.

The machine must be isolated. By definition, the code under review is not yet trusted. The model is also partly guided by text inside the pull request, where a prompt injection can hide.

Split the bot into two halves

Half Where it runs Holds
Reviewer A Runtime sandbox The checkout, the tools, no write token
Poster Your service The GitHub token that can comment

The sandbox never holds a token that can merge, push or comment. If the pull request persuades the model to do something hostile, the worst the model can reach is a throwaway machine.

The core loop

TypeScriptimport { Sandbox } from "withruntime";import { sandboxTools } from "withruntime/tools";const repo = "https://github.com/your-org/your-repo.git";const pr = 1234;await using sbx = await Sandbox.create({  diskMiB: 8192,  timeoutSeconds: 3600,  idlePauseSeconds: 900,  labels: { bot: "review", pr: String(pr) },  network: { internet: true, allow: ["github.com", "*.github.com", "registry.npmjs.org"] },});await sbx.exec(["git", "clone", repo, "repo"], { check: true, timeoutMs: 300_000 });await sbx.exec(["git", "fetch", "origin", `pull/${pr}/head:pr`], {  cwd: "/workspace/repo",  check: true,});await sbx.exec("git checkout pr && npm ci", {  cwd: "/workspace/repo",  check: true,  timeoutMs: 600_000,});// Facts to seed the model's context before it starts exploring.const changed = await sbx.exec("git diff --name-only origin/HEAD...pr", { cwd: "/workspace/repo" });const tests = await sbx.exec("npm test", { cwd: "/workspace/repo", timeoutMs: 900_000 });const lint = await sbx.exec("npx eslint $(git diff --name-only origin/HEAD...pr -- '*.ts')", {  cwd: "/workspace/repo",  timeoutMs: 300_000,});const tools = sandboxTools(sbx); // the model can run and read anything else it needsconsole.log(changed.stdout, tests.exitCode, lint.exitCode, tools.length);// ... the model's review loop runs here, and returns { summary, comments } ...
Pythonfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolsrepo = "https://github.com/your-org/your-repo.git"pr = 1234with Sandbox.create(    disk_mib=8192,    timeout_seconds=3600,    idle_pause_seconds=900,    labels={"bot": "review", "pr": str(pr)},    network={"internet": True, "allow": ["github.com", "*.github.com", "pypi.org", "*.pythonhosted.org"]},) as sbx:    sbx.exec(["git", "clone", repo, "repo"], check=True, timeout_ms=300_000)    sbx.exec(["git", "fetch", "origin", f"pull/{pr}/head:pr"], cwd="/workspace/repo", check=True)    sbx.exec("git checkout pr && pip install -e '.[test]' ruff", cwd="/workspace/repo",             check=True, timeout_ms=600_000)    changed = sbx.exec("git diff --name-only origin/HEAD...pr", cwd="/workspace/repo")    tests = sbx.exec("python3 -m pytest -q", cwd="/workspace/repo", timeout_ms=900_000)    lint = sbx.exec("ruff check $(git diff --name-only origin/HEAD...pr -- '*.py')", cwd="/workspace/repo")    run, read, write, ls = sandbox_tools(sbx)    print(changed.stdout, tests.exit_code, lint.exit_code)    # ... the model's review loop runs here, and returns summary and comments ...

Seeding the context with the changed files, the test result and the lint result saves the model several tool calls. From there, the model uses the four sandbox tools to read callers, run a single test, or try an edge case the author did not.

Post the review from your service

GitHub's POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews takes a body, an event of APPROVE, REQUEST_CHANGES or COMMENT, and comments with a path, a line and a body each. GitHub warns that creating content too quickly there can trigger secondary rate limits, so post one review per pull request, not one call per comment.

TypeScripttype Finding = { path: string; line: number; body: string };export async function postReview(  owner: string,  repo: string,  pr: number,  summary: string,  findings: Finding[],) {  await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${pr}/reviews`, {    method: "POST",    headers: {      Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,      Accept: "application/vnd.github+json",    },    body: JSON.stringify({ body: summary, event: "COMMENT", comments: findings }),  });}

Use COMMENT, not APPROVE, until you trust the bot. A person still merges.

Private repositories

To let the sandbox clone or read a private repository without holding a token, store the token as a Runtime secret limited to GitHub's hosts. Every sandbox gets a placeholder in its environment. The proxy puts the real value into HTTPS requests to those hosts only (secrets sandboxes never see). Give the token read access only. This is the reviewer half.

What a review bot needs

Need How Runtime covers it
Running untrusted pull request code A Firecracker microVM with its own kernel per review
A token the model cannot leak Secrets added by the proxy; the guest holds a placeholder
Tools for any model framework sandboxTools(sbx) or sandbox_tools(sbx), and adapters for ten frameworks
Tests with a database or queue sudo enable-docker, then Compose, inside the same sandbox
Waiting on the model $0.03125 an hour at 2 vCPU, 4 GiB; idlePauseSeconds pauses when idle
A second look after new commits Pause keeps the checkout and installs; the next call wakes it
Many open pull requests 100 sandboxes at the same time on a paid account

What it costs

Assume 2,000 reviews a month. Each review keeps a 2 vCPU, 4 GiB sandbox running for 15 minutes, mostly waiting on the model. Install, tests and lint use 120 CPU-seconds:

CPU
2,000 × 120 s / 3,600 × $0.025
$1.67
Memory
2,000 × 900 s / 3,600 × 4 GiB × $0.0075
$15.00
Total
$16.67

Under a cent a review, at $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour (pricing). Memory dominates because most of a review is the model reading. That is why a sandbox that pauses when idle pays off. Your model provider bills the model tokens. Every account has 100 free hours of a 1 GB machine in its first 30 days, with no card.

Sources

Related: CI for agent pull requests, static analysis agent, coding agent sandbox, Claude Code in a sandbox.

Facts on this page were checked on 26 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card