Review Pull Requests Automaticallypublic

Last verified 22 Sep 2026

DigitalOcean Harness Runtime combines the functionality of a lightweight microVM, built-in tools like chromium and a coding sandbox needed by agents to do work. The product offers rich lifecycle APIs that persist conversational history and working state across sessions, with pause/resume/fork semantics so that developers can control costs and adapt workflows to the nonlinear quirks of agentic work. See What You Can Build for example use cases.

A first-pass review catches the routine problems before a human spends attention on them. This example runs Claude Code from a GitHub webhook: a pull request opens, a session starts, the agent reads the diff and posts its notes to Slack, and the session is torn down.

The important difference from Refactor a Repository is that nobody is at a terminal. That changes how permissions have to be written.

Write a Spec That Can Run Unattended

There is no one to answer an approval prompt on a triggered run, so triggers reject a spec that could produce one. With --session-mode fresh, creating a trigger fails if the spec’s permissions default is ask, if any rule uses action: ask, or if there is no permissions block at all.

The pattern that works is an allow default with explicit denies for anything the agent must never do. Save the following as pr-review.yaml:

name: pr-reviewer
agent: claude-code
size: mars-2vcpu-4gb
repos:
  - your-org/your-service
env:
  HARNESS_INFERENCE_MODEL: anthropic-claude-5-sonnet
secrets:
  HARNESS_INFERENCE_API_KEY: ${HARNESS_INFERENCE_API_KEY}
  GITHUB_TOKEN: ${GITHUB_TOKEN}
egress:
  - github.com
  - api.github.com
permissions:
  default: allow
  rules:
    - tool: file.write
      action: deny
    - tool: bash
      match: { command: "rm -rf *" }
      action: deny

Model access works the same as in Refactor a Repository: the model access key in HARNESS_INFERENCE_API_KEY is captured when the trigger is created and reused on every firing, so nothing has to supply a credential when a pull request opens.

file.write is denied because this agent’s job is to read and report. It clones the repository, reads the diff, and writes a summary as its final output. Nothing it does should change the checkout.

The choice of adapter matters for that deny. A permission rule naming a tool the adapter does not declare is rejected at session create rather than ignored, and the Codex CLI adapter declares only bash. This spec uses claude-code, which declares file.write, so the rule is enforceable. See Agent Adapters for what each adapter supports.

Warning

ask is not a safety control on an unattended run. If a prompt does occur at run time, the platform approves it automatically so the run can finish, and records it as auto-approved in the transcript. Anything the agent must never do needs a deny rule.

Create the Trigger

Create a webhook trigger bound to that spec. --provider github tells the platform how to verify the signature on inbound deliveries:

doctl harness-runtime triggers create \
  --kind webhook \
  --name pr-reviewer \
  --session-mode fresh \
  --spec pr-review.yaml \
  --provider github \
  --prompt "Clone your-org/your-service and review this pull request: {{.pull_request.html_url}}. Report correctness risks, missing tests, and anything that looks unintentional. Be brief." \
  --output-mode slack \
  --output-slack-webhook <your-slack-incoming-webhook-url>

--session-mode fresh starts a new session per delivery and tears it down afterward, which is what you want when reviews are independent of each other.

The prompt can reach into the webhook payload. {{.pull_request.html_url}} pulls a single field out of the JSON body, and {{payload}} embeds the whole delivery. A field that is not present becomes an empty string.

The response includes the trigger’s webhook URL and secret. The secret is shown once, so copy both values and paste them into your repository’s webhook settings on GitHub before you move on. If you lose the secret, issue a new one with doctl harness-runtime triggers rotate-secret.

Check That It Ran

Every firing is recorded as an execution:

doctl harness-runtime triggers list-executions <trigger-id>
doctl harness-runtime triggers get-execution <trigger-id> <execution-id>

get-execution shows the inbound payload alongside the agent’s output, which is usually enough to tell a bad prompt from a bad payload. Each execution also records its session ID, so you can replay the whole transcript:

doctl harness-runtime logs <session-id>

Adapt It

To do this Change this
Run on a schedule instead of an event Use --kind cron with --cron-expr "0 9 * * 1" and --timezone. Scheduled firings have no payload, so the prompt cannot use {{payload}} or {{.field}}.
Deliver somewhere other than Slack Use --output-mode email with --output-email, or none if the run’s own side effects are the point.
Keep context between firings Pause a session and bind it with --session-mode reuse and --bound-session-id. The agent keeps its workspace and history across firings.
Take a source other than GitHub Use --provider gitlab, or custom for plain HMAC. Run doctl harness-runtime triggers list-providers for the current list.
Let the agent comment on the pull request Give it GitHub tool access through Action Gateway and allow the specific tool in permissions.
Stop it temporarily doctl harness-runtime triggers pause <trigger-id>. Resuming does not replay firings missed while paused.

We can't find any results for your search.

Try using different keywords or simplifying your search terms.