---
title: "ai.oz0/pr-reviewer"
description: "Pull request reviews with ai.oz0/github's read functions, the tests run at the head commit in a sandbox of their own, also for a webhook's review, and a rubric."
canonical_url: "https://docs.oz0.ai/build/plugins/verified/pr-reviewer"
---
# ai.oz0/pr-reviewer

> Pull request reviews with ai.oz0/github's read functions, the tests run at the head commit in a sandbox of their own, also for a webhook's review, and a rubric.

For: tenant administrators and plugin users.

`ai.oz0/pr-reviewer` reads a pull request, runs its tests and returns findings with a verdict. Use it to gather review evidence before a person decides what to do with the pull request.

## `oz0-pr-reviewer`

The agent takes `{"repository": "owner/name", "pull_number": 42}`. It reads the pull request, its diff, files and checks with [ai.oz0/github](https://docs.oz0.ai/build/plugins/github)'s read functions (`oz0_github.get_pull_request`, `get_pull_request_diff`, `list_pull_request_files`, `get_file`, `list_checks`), downloads its head commit with `oz0_github.download_archive`, runs the tests with `oz0_pr_reviewer.run_tests`, and answers with a review in a fixed shape.

It has no function that writes, so a pull request whose text asks it to approve, label or merge has nothing to call. A judge holds the review to a rubric before the job ends ([outcome checks](https://docs.oz0.ai/improve/outcome-checks)); a review that fails goes back once with the reasons.

The rubric is `plugins/pr-reviewer/rubrics/review.md` in the private repository. Reading its source requires repository access; public source downloads are coming soon.

## `oz0_pr_reviewer.run_tests`

This function takes the archive `download_archive` returned: `archive: oz0-file:sha256:<hex>`, at most 32 MiB. The runtime hands it to the function as a file of its own.

The function unpacks the archive with every member checked in a separate sandbox. That sandbox has no network and writes only to the call's directory. The function finds how the checkout runs its tests: Go, pytest, unittest or npm.

The tests run in their own [sandbox tier 1](https://docs.oz0.ai/concepts/security/sandbox-tiers#sandboxes). No secret, setting or node process is visible, including the tool server. Network access follows only the tenant's egress allowlist. Writes stay in the checkout, which is deleted afterwards.

Dependencies are installed there first: a Python project that declares any gets a virtual environment with the project and its `test`, `tests`, `testing` or `dev` extras and its requirements files (`pip install`), an npm project `npm ci --ignore-scripts` (`npm install --ignore-scripts` without a lockfile); a Python project without dependencies runs offline with the plugin's own pytest. It refuses on a node without tier 1 (it never runs a pull request's code at tier 0); the agent then reviews the diff and says the tests did not run, and why.

- `oz0-pr-review`, the flow, runs the agent and then posts the review as a comment with `oz0_github.comment`, the
only write anything in this plugin makes. A retried step does not comment twice.

## Install

Its agent `oz0-pr-reviewer` asks for the tenant's `default` [model alias](https://docs.oz0.ai/spend/models-and-keys/aliases#the-standard-aliases) with tools, never a vendor's model, so it runs on whichever provider the tenant set up first; map it elsewhere with `oz0 model alias set default <provider>/<model>`.

```bash [Terminal]
oz0 plugin install hub:ai.oz0/github
oz0 plugin install hub:ai.oz0/pr-reviewer
oz0 run oz0-pr-review '{"repository": "acme/shop", "pull_number": 42}'
```

The second install is refused once with what pr-reviewer asks of ai.oz0/github (its five read functions and
`comment`) and a digest; a tenant admin grants it by installing again with `--accept-uses <digest>`
([uses](https://docs.oz0.ai/reference/plugin-manifest)). Connect ai.oz0/github first ([its page](https://docs.oz0.ai/build/plugins/github)).

pr-reviewer has no settings and holds no secret: every call to GitHub, the archive's download included, is
ai.oz0/github's, with the tenant's GitHub App (or its `GITHUB_TOKEN`), so a private repository and a GitHub
Enterprise Server need nothing more than github's connection. Earlier builds read `PR_REVIEWER_GITHUB_TOKEN` and
`PR_REVIEWER_GITHUB_API_URL`; if you set them, delete them (`oz0 secret delete PR_REVIEWER_GITHUB_TOKEN`).
Tests that download dependencies need their registries on the tenant's allowlist, such as `proxy.golang.org` and
`sum.golang.org`, `pypi.org` and `files.pythonhosted.org`, or `registry.npmjs.org`; when one is missing, the result
says "not run: installing the dependencies failed" and names what the allowlist refused.

## On a node with sandbox tier 1

`oz0_pr_reviewer` starts sandboxes of its own, and a sandbox cannot start another, so on a node with the default
`--plugin-sandbox strict` it does not start until the node's operator allows it on that node:

```bash [Terminal]
oz0-node service install --plugin-sandbox-exempt pr-reviewer   # or: oz0-node run --plugin-sandbox-exempt pr-reviewer
```

Allow it before the install: an install's quality gate runs the plugin's evals on a test node, and a node that holds
the tool back fails them, with this reason on the gate's line. Until then the plugin's status and the node's findings
say so. The tests still run in their own sandbox; the server that starts them runs as the node's user, which is why
that is the operator's choice.

## Reviews a webhook starts

A review that a pull request's webhook or a routine starts runs its tests too. Such a job runs its tools in a sandbox of its own (outside input, [sandboxes](https://docs.oz0.ai/concepts/security/sandbox-tiers#sandboxes)), and a sandbox cannot start another, so `oz0_pr_reviewer` declares `sandbox: own`: in such a job its calls run in pr-reviewer's own server on the job's tier-1 home node, outside the job's sandbox, and start the tests' sandbox from there.

That is allowed only because the plugin holds nothing (no secret, setting, connection or egress), which lint, the node and the edge each check; the edge records every such call in the [audit trail](https://docs.oz0.ai/govern/audit-trail) as `tool.own_sandbox`, with its job, function and node. pr-reviewer's server never reads the pull request's bytes itself: it copies the archive into the call's directory and unpacks it in a sandbox.

Its server runs up to four reviews' tests at once. ai.oz0/github's functions in the same job still run in the job's sandbox, whose egress allows `api.github.com` and `codeload.github.com`, where GitHub sends the archive; on GitHub Enterprise Server add its host (and `codeload.<host>` with subdomain isolation) to the tenant's egress list. A copy of pr-reviewer sent to a cluster as a private candidate (`oz0 dev --server`) does not run tests: a candidate's tool servers always run in the node's sandbox ([private candidates](https://docs.oz0.ai/build/plugins/develop-and-test/candidate-checks#try-it-on-a-real-cluster)), so the review says the tests were not run.

A flow of your own team's plugin that reviews each new pull request (its plugin declares `uses: {pr-reviewer: {agents: [oz0-pr-reviewer]}}`):

```yaml [flows/review-prs.yaml]
name: review-prs
description: Reviews a pull request when it is opened
trigger:
  event: {source: github, types: [pull_request], filter: "event.action == 'opened'"}
steps:
  - id: review
    agent: oz0-pr-reviewer
    input:
      repository: ${{ trigger.event.attributes.repo }}
      pull_number: ${{ trigger.event.attributes.number }}
output: ${{ steps.review }}
```

Add a step with `oz0_github.comment` to post it, as `oz0-pr-review` does ([flows](https://docs.oz0.ai/build/parts/flows#start-one-from-an-event)).

The tests may run for 30 minutes (`timeout_s`, ten by default); one call of the tool may take 35 (`timeout: 35m` in
its manifest).

Run the agent on Linux nodes with bubblewrap, or Macs (`oz0-node sandbox check` shows a node's tier). A
job a person starts is not routed by tier, so in a fleet with tier-0 nodes, label the tier-1 nodes and require the
label in an overlay of the agent.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
