Developer Tools
AI Code Review
Cursor Agent SDK

How to Build a PR Review Agent with the Cursor Agent SDK

Reviewing pull requests is the most repetitive expert task on a team: read the diff, check it against the ticket, judge whether it matches the codebase's conventions, and write findings down. A surprising amount of that pipeline is now scriptable with the Cursor Agent SDK — and the pattern is small enough to build in a weekend.

TL;DR

You glue three pieces together: a script that loads open pull requests and computes the merge-base diff, a prompt that hands the agent the diff plus Jira requirements and your code style preferences with a locked reply shape (Summary / Issues / Verdict), and the Cursor SDK's four-call loop — launch_bridge, create_agent, send, wait — plus retries, state, and token-cost tracking so it runs unattended. The result is a reviewer that handles the mechanical pass and leaves the judgment calls to humans.

12 min read

LogNroll Team

Developer Experience

This article walks through a real production pattern: an automated Bitbucket PR reviewer built on the Cursor Agent SDK, with local repo clones, Jira requirements context, structured verdicts, and cost tracking. The snippets are condensed from that system — adapt the host-specific parts to GitHub or GitLab.

Why automate the mechanical pass

Human review time is the bottleneck of every merge pipeline, and a large share of review work is mechanical: checking that the diff matches the ticket, that it follows the repo's conventions, that nothing unrelated snuck in, that obvious correctness and security traps were avoided. That pass is well-defined enough for an agent — and automating it frees the people who review for the parts that actually need judgment: architecture, trade-offs, product semantics.

The goal is not to replace the reviewer. It is to give every PR a competent first pass within minutes of being opened, with the same standards every time, and to re-check it when the branch moves. A bot that catches the regression before a human looks at the PR is worth more than one that argues about style after a human has already read it.

What the Cursor Agent SDK gives you

The SDK turns the Cursor agent — the same one that works inside the editor — into a library you call from a script. The core is a bridge process that attaches to a workspace folder, plus a handful of RPCs: CreateAgent spawns an agent with a model, credentials, and optional MCP servers; Send hands it a prompt; then you stream status, tool calls, thinking, and usage messages until the run finishes and returns the result.

Engineering note

Because the agent runs against a local clone, it can read the real codebase around the diff — imports, callers, types — instead of reviewing a diff in a vacuum. That is the single biggest quality lever in this design: LocalAgentOptions(cwd=repo_dir) is what makes the review grounded in code, not just in text.

The pipeline at a glance

Load the open pull requests

Pull the PR list from your git host (Bitbucket, GitHub, GitLab) and filter to OPEN, non-draft PRs. Skip release trains automatically — a title containing "RELEASE" never gets reviewed.

Verify each PR is still alive on origin

An export can be stale: the branch was merged, the PR closed, the source branch deleted. Resolve both branch tips against the remote, check that the source is not already an ancestor of the destination, and drop what is gone before spending tokens on it.

Compute the real diff

Review the change, not the branch: diff the merge-base of the two branch tips against the source tip (the classic three-dot diff), with a tip-to-tip fallback and automatic history deepening for shallow clones.

Gather requirements context

Extract ticket keys from the PR title, description, branch name, and commit messages, then fetch each ticket summary and description from Jira. The ticket is the source of truth the agent checks the diff against.

Run the agent with the full context

Launch a Cursor bridge into the cloned repo, create an agent, and send one prompt: PR metadata, Jira requirements, code preferences, diff stat, unified diff, and the exact reply shape you want back.

Post the structured review

Parse the verdict, tag the author when changes are needed, and post a comment on the PR. Persist state after every PR so a crash mid-run never loses a review or causes a duplicate comment.

Getting the diff right: review the change, not the branch

The diff is the agent's primary input, so it has to be the change — not the full divergence between two long-lived branches. The reliable way is the three-dot diff: compute the merge-base of the destination and source branch tips, then diff that base against the source tip. Everything the source branch adds, edits, or removes relative to where it forked is exactly what the PR is.

# merge-base of the two tips, then diff base...source
base=$(git merge-base origin/main origin/feature)
git diff --stat "$base" origin/feature
git diff "$base" origin/feature          # unified diff for the prompt

# fallback when history is shallow and no merge-base exists:
# deepen (or unshallow) the clone first, then retry; last resort:
git diff origin/main origin/feature      # tip-to-tip

Two practical traps matter in automation. First, shallow clones often hide the merge-base — deepen the clone when git merge-base comes back empty before falling back to tip-to-tip. Second, cap the diff: a 30-file refactor as a single 50,000-line prompt is both expensive and a worse review. Truncating at a line budget and noting the truncation keeps the agent's attention on the meaningful parts.

Context is what makes the review specific

A raw diff review reads like a linter. To get findings a team can act on, the prompt needs the same context a human reviewer collects before commenting: what the PR claims to do, what the ticket requires, and how this codebase is supposed to be written.

The diff itself

Send the diff stat plus the unified diff of the merge-base…source range. Truncate at a sane line budget (8,000 lines is a reasonable default) so giant PRs degrade gracefully instead of blowing up the prompt.

Code style & principles

A plain Markdown file that encodes how your team reviews: naming conventions, error-handling rules, what counts as a blocking issue. The agent applies it when judging severity instead of inventing its own bar.

Jira requirements

Each detected ticket with title, status, and description. The prompt tells the agent to treat the ticket as the source of truth: does the diff fulfill it, does it include unrelated work, does it miss requirements?

Prior review & developer feedback

On re-review, the prompt carries the previous findings, a digest of the discussion since, and the developer replies. The agent re-validates open points and does not re-litigate resolved ones — so follow-ups stay short.

Lock the reply shape

An agent is only as useful as its output format. The prompt pins the reply to three sections, demands CRITICAL and MAJOR issues only (no nits, no style commentary), and specifies a one-line verdict. Structured output is what makes the result postable, comparable across PRs, and machine-readable for the state machine.

Reply in markdown with ONLY these sections:
### Summary
2-4 sentences: what changed, and whether it matches the Jira requirements.
### Issues
CRITICAL/MAJOR only. Each bullet: `[SEVERITY] file/symbol — problem; fix`.
### Verdict
One line: `approve` / `request changes` / `needs discussion` — brief reason.

If the verdict is approve, end the reply with this exact line:
![APPROVED](https://example.com/approved.png)

Why CRITICAL/MAJOR only

Bots that report every nit get ignored, and ignored bots create noise that trains humans to skip all review comments. Restricting the output to blocking issues keeps the signal high and the comment short — and it makes the verdict trivially parseable: approve, request changes, or needs discussion.

The SDK call itself

With the diff and the prompt ready, the agent call is small: launch the bridge into the repo, create an agent with a model and the repo as its workspace, send the prompt, stream the run, and read the final result. Optional MCP servers (for example an LSP bridge so the agent can resolve symbol definitions and references instead of grepping) attach at creation time.

from cursor_sdk import Client, AgentOptions, LocalAgentOptions
from cursor_sdk_helpers import cursor_agent_model

local = LocalAgentOptions(cwd=str(repo_dir))
client = Client.launch_bridge(workspace=str(repo_dir), local=local)
try:
    agent_options = AgentOptions(
        model=cursor_agent_model("composer-2.5", default="composer-2.5"),
        api_key=api_key,
        local=local,
        mcp_servers=mcp_servers or None,   # optional LSP bridge
    )
    with client.create_agent(agent_options) as agent:
        run = agent.send(prompt)
        for message in run.messages():     # status / tool_call / thinking / usage / assistant
            log(message)                   # progress + token usage as it streams
        result = run.wait()
        review = str(result.result)
finally:
    client.close()

The model matters less than the context: the same prompt shape that works on the default model also works on cheaper and faster ones, which is exactly the knob you want for cost control (more on that below). The cursor_agent_model helper normalizes a model name with a fallback so a typo or a retired model never silently downgrades your reviews.

Robustness: what makes it run unattended

Retry the flaky, fail the definite

Network errors, rate limits, and 5xx responses are retried with backoff (respecting retry_after when the API tells you to wait). Authentication and 4xx errors fail fast — retrying them wastes tokens.

Re-review only when something changed

Skip PRs that were already reviewed unless the source branch tip moved or a developer replied. Mentat’s own comments never count as a change, so the bot cannot chase its own tail.

State survives crashes

A state file is flushed after each PR. If the run dies mid-way, already-completed reviews are not repeated, and a review that finished but could not be posted is deferred and retried later without re-running the agent.

One review, one comment

A verdict that approves ends with an explicit marker line, and the review is posted as a single comment (split into parts only when it exceeds the comment size limit). No edit storms, no duplicate threads.

Error handling is where the SDK surface matters most. The library distinguishes retryable failures — NetworkError, RateLimitError, internal 5xx states, and anything marked retryable on the exception — from definite ones like authentication errors:

from cursor_sdk import CursorAgentError, NetworkError, RateLimitError

def should_retry(exc):
    if isinstance(exc, (NetworkError, RateLimitError)):
        return True
    if isinstance(exc, CursorAgentError):
        return getattr(exc, "is_retryable", False)   # 5xx, upstream, unavailable
    return False

# on retryable failure: sleep (retry_after if present) and re-run the agent;
# on anything else: mark the PR as error and move on.

Cost control: know what every review costs

A review bot burns tokens on every run, so cost visibility is not optional. The SDK reports token usage per run (input, output, cache reads and writes); the pipeline converts that into an estimated dollar cost using published per-model rates, and logs it with every review. Two levers keep the bill sane: model choice and diff budget.

Estimate cost from usage, not guesses

Per-1M-token rates for the default model are roughly $0.50 input / $0.20 cache-read / $0.50 cache-write / $2.50 output. A typical review lands well under a dollar; big diffs are where the spend concentrates — which is exactly why the diff budget exists.

Pick the cheapest model that keeps quality

Run the same prompt through the model family once, compare verdict quality on real PRs, and set the cheapest passing model as the default. Individual plans ship with an included monthly API pool, so most review volumes fit inside the plan you already pay for.

The feedback loop that pays for itself

Log every review's token usage and estimated cost next to its outcome. After a few weeks you will know the average cost per caught CRITICAL/MAJOR issue — and that number is almost always far below the cost of the incident the review prevented. Treat the bot like any other test: it is cheap insurance that runs on every PR.

What to ship in the first version

A minimal v1 is about two hundred lines: load open PRs, verify them on origin, three-dot diff with truncation, build the prompt from PR + Jira + preferences, call the agent, post the comment, save state. Start without the LSP bridge and without follow-up re-review, then add them once the baseline is stable:

  • Filter to OPEN, non-draft PRs and skip release trains automatically
  • Verify PRs against origin before diffing — stale exports cost tokens
  • Diff merge-base…source (three-dot), never tip-to-tip, to review only the change
  • Deepen shallow clones when the merge-base is missing from local history
  • Extract Jira keys from title, description, branch, and commit messages
  • Load a code style & principles file so severity judgments match your team
  • Lock the reply shape: Summary / Issues / Verdict, CRITICAL/MAJOR only
  • Truncate the diff (8,000 lines) and monitor token usage per review
  • Persist state after each PR; defer comment posting when auth is down
  • Re-review only when the branch tip or developer replies changed

Common mistakes

Reviewing tip-to-tip instead of merge-base

A branch that drifted behind main produces a diff full of other people's merges. The three-dot diff keeps the review on the PR's own changes — and keeps the findings attributable to the right author.

Reviewing without requirements

Without the ticket, the agent can only judge the diff against itself. With the Jira description in the prompt it can catch the missing validation, the off-by-one against the spec, the half-implemented acceptance criterion.

No state, no retries, no budget

A bot that double-comments after a crash, dies on a rate limit, or silently spends $40 on an unfiltered monorepo diff gets switched off within a week. State, retry policy, and a diff budget are features, not polish.

Where LogNroll fits

A PR review agent answers is this code right; session replay answers what did users actually experience. The two belong together. When the bot flags a checkout-path change as a CRITICAL regression risk, the fastest way to size the blast radius is to watch real sessions: replay the users who hit that flow, open the network tab for the failed calls, and check whether error groups on the same code path started growing. A review finding becomes actionable the moment you can see its effect on real users.

That is why we built LogNroll the way we did: recordings, error tracking, and network timelines live in one place, so a verdict from an automated reviewer — or a human one — can be validated against production behavior instead of argued about. For the capture pipeline behind it, see our session replay architecture guide; for turning a flagged regression into a debugging session, read When Error Logs Lie: Reproducing Production Failures with Session Replay, and for tracing the network side of a suspected regression, Network Tab + Replay: Debug Failed API Calls in Context.

Conclusion

Building a PR review agent on the Cursor Agent SDK is a small project with an outsized payoff. The pieces are simple once you see them: load and verify the PRs, compute the merge-base diff, assemble context from the ticket and your code preferences, lock the reply shape, and wrap the SDK's four-call loop in retries, state, and cost tracking. The result is a reviewer that is fast, consistent, and always available — one that takes the mechanical pass off your team's plate and leaves the judgment calls to the people who should be making them.