PR Guard
AI-driven code review that runs against a pull request's diff and surfaces severity-ranked findings, inline in Safeguard and optionally posted back to the pull request itself.
PR Guard
PR Guard is Safeguard's AI code review feature for pull requests. Point it at a pull request and it analyzes the diff, then returns a scored summary plus a list of comments — each tied to a specific file and line, categorized by severity and type, and optionally accompanied by a suggested fix. Results are shown in the Safeguard UI, and can also be posted directly onto the pull request on the provider side so reviewers see them inline where they already work.
PR Guard appears as its own tab, labeled PR Guard, on every project's detail page, alongside tabs like Findings — though review actions are only enabled once the project's connected repository is on GitHub (see below).
How it fits into a project
A project's PR Guard tab has two views:
- Pull request list — every pull request Safeguard can see for the project's repository, with a state filter and pagination.
- Review workflow — pick a pull request, run a review against it, watch it progress, and read the results across Overview, Files, Comments, and History tabs. Opening a specific pull request from the list takes you straight into this view for that PR.
PR Guard's current implementation is wired to GitHub pull requests. If a project's connected repository provider isn't GitHub, the tab shows a GitHub project required notice and review actions are disabled for that project.
The pull request list
The list view shows the pull requests Safeguard has fetched for the project's repository, with:
| Column | What it shows |
|---|---|
| PR / MR | The pull request number |
| Title | The PR title, with a link out to the PR on the provider |
| Status | Draft, Merged, Open, or Closed, derived from the PR's draft / merged / state fields |
| Branch | Source branch → target branch |
| Author | The PR author |
| Updated | Last-updated timestamp |
| Changes | Additions/deletions across the PR (+N / -N) |
Above the table, a State selector filters the list to open, closed, or all pull requests (defaults to open). Clicking a row opens that pull request's review workflow.
Under the hood this list comes from AiPullRequestSummary records — number, title, repositoryName, url, state, draft, merged, sourceBranch, targetBranch, headSha, baseSha, author, createdAt, updatedAt, changedFiles, additions, deletions — paginated per project (page is 1-indexed, 5 rows per page in the list view).
Triggering a review run
From the review workflow view, pick a pull request and click Run Review. A run request carries whatever the backend needs to identify and analyze the PR: the project, repository name/URL, pull request number (the only strictly required field), title and URL, source and target branch, head and base commit SHA, provider, language, and either a pre-supplied file/diff payload or nothing (in which case PR Guard fetches the diff itself from the provider). An options block controls run behavior:
| Option | Purpose |
|---|---|
maxComments | Caps how many comments the review returns |
useSampleFallback | Lets the run fall back to sample output if live analysis can't complete |
publishToProvider | When true, PR Guard also posts its comments onto the pull request on the provider (see Publishing back to the provider) |
In the UI, this last option is the Post to GitHub checkbox next to the Run Review button — check it before running if you want comments posted to the PR itself, not just shown in Safeguard.
Example: a run request
Clicking Run Review for PR #128 with Post to GitHub checked sends a request shaped like this (the UI only populates the fields it already knows — projectId, the PR number, and options — and lets PR Guard resolve the rest, including fetching the diff itself):
{
"projectId": "482",
"pullRequestNumber": "128",
"options": {
"maxComments": 16,
"publishToProvider": true
}
}The response returned immediately from this call is the freshly created run — typically still QUEUED, with empty files and comments arrays until analysis progresses.
Run lifecycle and live progress
A review run moves through a status returned on every poll:
| Status | Meaning |
|---|---|
QUEUED | The run has been accepted and is waiting to start |
RUNNING | Analysis is in progress |
COMPLETED / completed | The run finished successfully |
FAILED / failed | The run did not complete; check errorMessage |
Both upper- and lowercase forms of the completed/failed statuses exist on the type — the UI normalizes whatever it receives to uppercase before comparing, so you don't need to worry about casing when reading a status back; just don't assume one case is authoritative.
While a run is QUEUED or RUNNING, Safeguard polls it (about every 2.5 seconds) and shows:
currentStep— a short label for what the run is doing right now. The UI's pipeline view mapscurrentStepagainst an illustrative sequence —FETCHING_PR,FETCHING_FILES,ANALYZING,PERSISTING_RESULTS,PUBLISHING_PROVIDER(shown only when publishing was requested),COMPLETED— butcurrentStepis a free-form string on the type, not a closed enum, so treat this as the shape of progress reporting rather than a guaranteed set of values.progressPercentage— a 0–100 completion estimate, rendered as a progress bar.
A run also carries jobId / runId (identifiers for the run itself), and createdAt / startedAt / completedAt timestamps.
Example: a run's progression across polls
A single run's status/currentStep/progressPercentage might look like this across successive polls (illustrative values — exact steps and percentages aren't guaranteed):
| Poll | status | currentStep | progressPercentage |
|---|---|---|---|
| 1 | QUEUED | null | 0 |
| 2 | RUNNING | FETCHING_PR | 10 |
| 3 | RUNNING | FETCHING_FILES | 25 |
| 4 | RUNNING | ANALYZING | 60 |
| 5 | RUNNING | PERSISTING_RESULTS | 90 |
| 6 (publish requested) | RUNNING | PUBLISHING_PROVIDER | 95 |
| 7 | COMPLETED | COMPLETED | 100 |
If a run instead ends FAILED, the last poll carries errorMessage explaining why, and score/summary/comments are typically absent or empty — see Common errors.
Run results
Once a run completes, it returns:
score— an overall numeric score for the PR.summary— a short natural-language summary of the review.files— per-file results.comments— the individual review comments.warnings— any non-fatal issues encountered while producing the review (shown as inline alerts).source— where the reviewed content came from:github(PR Guard fetched the diff itself),provided_diff/provided_files(the request supplieddifforfilesdirectly), orsample_fallback/fallback(live analysis couldn't complete and, becauseuseSampleFallbackwas set, PR Guard returned sample output instead of failing the run outright).
Files
Each entry in files is an AiCodeReviewFileResult:
| Field | Description |
|---|---|
path | File path |
status | pending, analyzed, or skipped (the field isn't a strictly closed enum on the type, so treat other values as possible) |
changeType | e.g. added/modified/removed, as reported by the provider |
additions / deletions | Line counts for the file |
patch | The unified diff for the file, rendered in the Files tab's diff viewer |
reason | Present when status is skipped — why the file wasn't analyzed (also shown when no patch content is available) |
Comments
Each entry in comments is an AiCodeReviewComment, scoped to a file and (optionally) a line:
| Field | Description |
|---|---|
severity | critical, high, medium, low, or info |
category | security, bug, reliability, performance, maintainability, test, documentation, or architecture |
path / line | The file (and, if applicable, line) the comment applies to |
title / body | The finding's headline and detail |
suggestedFix | An optional proposed code change, shown as a distinct block under the comment |
confidence | An optional confidence score for the finding |
publishedToProvider / providerCommentId | Set once this individual comment has been posted to the provider (see below) |
The Overview tab rolls these up into a score, comment count, file count, a "High Risk" count (critical + high), and a risk breakdown by severity. The Comments tab lists every comment; the Files tab shows the diff for a selected file with that file's comments inline.
Example: a review comment
A critical-severity security finding on a specific line, with a suggested fix and provider publish state, looks like this:
{
"id": 4821,
"severity": "critical",
"category": "security",
"path": "src/api/auth/session.ts",
"line": 47,
"title": "Session token compared with non-constant-time equality",
"body": "The `===` comparison on the session token leaks timing information an attacker could use to guess valid tokens byte-by-byte. Use a constant-time comparison instead.",
"suggestedFix": "if (!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expected))) {\n throw new UnauthorizedError();\n}",
"confidence": 0.86,
"publishedToProvider": true,
"providerCommentId": "PRRC_10293"
}A lower-severity, informational comment with no fix and nothing published yet is just as valid — suggestedFix, confidence, publishedToProvider, and providerCommentId are all optional, so a minimal comment may only carry severity, category, path, title, and body.
Publishing back to the provider
By default, PR Guard results only appear in Safeguard. Checking Post to GitHub (setting options.publishToProvider: true on the run request) tells PR Guard to also post its comments directly onto the pull request on the provider, so reviewers see them inline where they already work rather than needing to visit Safeguard.
Publishing is asynchronous and best-effort relative to the run itself:
- The response carries
publishToProvider(whether publishing was requested for this run) andproviderReviewId(set once the review has actually been posted to the provider). - Individual comments carry their own
publishedToProviderandproviderCommentIdonce posted. - In the UI, a run with publishing requested shows "GitHub publish requested" until
providerReviewIdis populated, then flips to "Posted to GitHub" — the publish step can complete after the rest of the analysis, so don't assume aCOMPLETEDrun has already published. - Because the run-level
providerReviewIdand each comment'spublishedToProviderare set independently, it's possible for the run to show as posted while a handful of individual comments still showpublishedToProvider: false(or vice versa, briefly, while the publish step is in flight) — check the field at the level you actually care about rather than assuming they always flip together.
Review history
The History tab lists past runs for the project — pull request number and title, run date, comment count, status, and score — pulled from listAiCodeReviewRuns (paginated, 0-indexed, 10 per page by default). Selecting a past run loads its full result (via getAiCodeReviewRun) back into the Overview/Files/Comments tabs, so you can revisit an earlier review without re-running it.
FAQ & Troubleshooting
The tab shows "GitHub project required" and I can't run a review. PR Guard's current implementation is wired to GitHub pull requests. This appears when the project's connected repository provider isn't GitHub — connect the project via a GitHub-backed integration to use PR Guard.
A run says COMPLETED but I don't see it on the pull request on GitHub.
Check whether you enabled Post to GitHub for that run — publishing only happens when publishToProvider was set on the run request. If it was set, publishing can finish slightly after analysis; look for providerReviewId on the run (shown as "Posted to GitHub" vs. "GitHub publish requested" in the UI) before assuming it failed.
A run is stuck in RUNNING or QUEUED.
The UI polls active runs automatically; give it a few cycles. If a run ends in FAILED, check the errorMessage field surfaced in the run's progress panel for why it didn't complete.
Why do some files show no diff?
A file's status may be skipped (see its reason) or the provider may not have returned patch content for it — both are shown directly in the Files tab instead of an empty diff.
I set publishToProvider: true and the run shows a providerReviewId, but a comment I'm looking at still has publishedToProvider: false.
The run-level providerReviewId and each comment's publishedToProvider / providerCommentId are set independently — the review-level post to the provider and the per-comment posts aren't guaranteed to complete in lockstep. Treat the run as "posted" only for what providerReviewId covers, and check each comment's own publishedToProvider before assuming every comment made it onto the provider PR.
A completed run's source is sample_fallback (or fallback) instead of github.
This means live analysis couldn't complete and, because the request had options.useSampleFallback: true, PR Guard returned sample output instead of failing the run outright. Treat the score, summary, and comments on that run as illustrative rather than a real analysis of the PR's diff — rerun once the underlying issue (e.g. a diff PR Guard couldn't fetch) is resolved, or drop useSampleFallback if you'd rather the run fail loudly (FAILED with errorMessage) than return sample content.
Related
- Application Security Testing (SAST & DAST) — first-party static and dynamic scanning, distinct from PR Guard's diff-scoped AI review.
- Unified Findings & Feature Flags — how PR Guard's findings relate to the rest of Safeguard's consolidated findings model.
- AutoTriage — cross-scanner deduplication and noise reduction for findings volume.
- Griffin AI — the purpose-built LLM underpinning Safeguard's AI-driven analysis features.
- CI/CD Integration — wiring Safeguard into your pipelines alongside pull-request-level review.
- GitHub Integration — connect the GitHub-backed repository a project needs before PR Guard's review actions are enabled for it.
AutoTriage
Cross-scanner deduplication and noise reduction that cuts finding volume with a measured reduction metric — and never suppresses malware or secrets findings.
Unified Findings & Feature Flags
Every security engine reports into one unified findings model, browsed from a single consolidated Findings tab with a scanner filter, and independently admin-toggleable via server-enforced feature flags.