PR Review
Read the diff, draft a verdict, and submit it via gh pr review. The
submission step is not optional — it is the definition of "done" for this
skill.
Contract
For coding-enabled server-bound poller and heartbeat turns, use the typed
pr_metadata, pr_files, pr_diff, pr_checks, pr_reviews, pr_comments,
and pr_review_requests tools instead of gh. Submit through
pr_submit_review or pr_inline_review_comment. These tools intentionally have
no repository or PR selector: the immutable event authority supplies the only
target. The CLI workflow below applies only to an interactive operator turn
without a bound repo/PR tool surface.
Trigger: Operator asks to review a PR; or the github-activity poller fires a
pr_opened / review_requested event for a PR authored by someone other than
the agent itself ($MIMIR_GITHUB_SELF_LOGIN) — the agent doesn't self-review.
Requires: PR number; gh CLI authenticated (verified by gh auth status);
diff accessible — either under 20k lines via gh pr diff, or file list via
gh pr view <n> --repo <owner>/<repo> --json files for large PRs (gh api is
not admitted on a poller turn).
Guarantees:
- A review is posted to GitHub (not just output to turn text) via
gh pr review. - Verdict is one of APPROVE / REQUEST_CHANGES / COMMENT — never left unsubmitted.
- Every blocking finding is anchored to a specific file + line in the diff.
- Full test suite passes (or a clean explanation of why failures are pre-existing and tracked) before APPROVE is submitted.
Does not: Merge PRs; modify source code directly; auto-resolve conversations;
review PRs it authored (when $MIMIR_GITHUB_SELF_LOGIN matches the PR author,
a different reviewer is needed).
When the trigger is poller:github-activity, perform the review in the current
turn. Do not call approve_declassification: the poller is subject to the taint
gate and cannot approve release of its own inputs. Do not call
spawn_open_code or another spawn tool: a review turn does not delegate into a
coding process. A denied attempt is a working security boundary, not a reason to
request either capability.
Step-by-step
1. Get PR context
gh pr view <num> --json number,title,body,author,baseRefName,headRefName,headRefOid,state,additions,deletions,changedFiles
Note the headRefOid (SHA) — you need it if you fetch file content via the
API.
2. Get the diff
gh pr diff <num>
The diff is the authoritative source for what changed. Read it carefully before reaching for local file reads.
For PRs whose diff exceeds ~20k lines use the file-list fallback:
gh pr view <num> --repo jasoncarreira/mimir --json files
Filter the returned JSON yourself rather than with --jq (see the note below).
--jqis not available on poller or scheduled turns. The trusted-service shell profiles do not admit it:ghevaluates the filter in-process and jq'senvbuiltin reads the process environment, so the option is a credential read rather than an output formatter. Use--json <fields>and filter the returned JSON yourself. Piped and bracketed filters were already refused on those turns anyway, because|,[and]are shell metacharacters.
3. Fetch source files only when necessary — and safely
Use local Read only when you need extended context beyond what the diff
shows (e.g., the full function surrounding a changed line, a test file
you want to read in full). Before calling Read, ask: "is this file
on the currently-checked-out branch?" If the PR branch is not checked
out locally, the file may not exist or may show the wrong version.
Correct alternatives when the local checkout may be stale or on a different branch:
gh pr diff <num> --repo jasoncarreira/mimir
On a poller turn, use the diff plus a local Read of the checked-out branch.
When the local checkout does not contain the reviewed head, fetch_url may read
an exact file at that head using
https://raw.githubusercontent.com/<owner>/<repo>/<headRefOid>/<path>. The owner
and repo must be one of the server-configured GITHUB_REPOS; arbitrary hosts,
repositories, and path traversal remain denied. fetch_url is GET-only and the
returned content remains untrusted. Do not substitute gh api: it is outside
the review profile, and pipes or compound shell commands are not admitted.
fetch_url returns a cache path under /attachments/fetch-cache/. Read that
path with read_file; use its offset and limit for an exact line range, or
use grep with output_mode="content", before_context, and after_context
for bounded context around a match. Always pass the documented absolute virtual
/attachments/fetch-cache/... form to read_file; although the backend also
resolves relative attachments/fetch-cache/... paths, the file-tool schema
requires an absolute path. Do not use cat,
head, sed, awk, python, or jq to slice fetched content: those
shell forms are not the bounded file-read interface and the observed slicing
commands are refused; awk/python can execute code and jq can inspect the
process environment, so widening their admitted forms is not a substitute.
Never replace fetch_url with curl; direct curl to api.github.com would
bypass the repository-bound egress adapter and redirect re-check, so it remains
refused.
If a local Read returns "file does not exist": do NOT bail. Log it
in your notes ("couldn't read locally — reviewing from diff only")
and continue. The diff has the change; local context is supplemental.
This is the most common cause of review turns that draft but never submit. A file-not-found error is never a reason to stop before submitting.
4. Draft the review
Write a review that covers:
- Verdict — approve, request changes, or comment (see submission flags below). Choose based on correctness + blocking issues, not tone.
- Summary — 2-4 sentence overview of what the PR does and whether the approach is sound.
- Specific observations — grouped by file or concern. Use the
> quoteconvention to anchor suggestions to specific lines. - Test coverage note — are the tests adequate? Are they testing the right behavior?
- Non-blocking suggestions — style, naming, minor structural notes. Label them explicitly as non-blocking so they don't hold up merge.
5. Submit — MANDATORY last step
Reserve budget for submission. Once you know the verdict, stop optional
exploration and submit before doing long validation, repeated polling, or
extra context reads. If the tool-call counter is visibly high (around 90+
on a 120-call budget), keep roughly 10-15 calls for the required side
effect (gh pr review), submission verification, and wrap-up. A review
with perfect extra evidence that never reaches GitHub is worse than a
bounded review that lands with an honest validation note.
Write the body to a file under the agent scratch root first, then submit it with
--body-file. On a poller turn this is the only form that works: the profile
execs one argv with shell=False, so a heredoc, a $(...) substitution, and an
inline multi-line --body are all refused — the body is read from the file
during authorization and never re-opened at execution.
# 1. Write the body (use the Write tool, or a single redirect-free command)
# to <scratch>/pr-<num>-review.md
# 2. Submit exactly one of these, as a single command:
gh pr review <num> --repo jasoncarreira/mimir --approve --body-file <scratch>/pr-<num>-review.md
gh pr review <num> --repo jasoncarreira/mimir --request-changes --body-file <scratch>/pr-<num>-review.md
gh pr review <num> --repo jasoncarreira/mimir --comment --body-file <scratch>/pr-<num>-review.md
The body file must resolve beneath the agent scratch root, be a regular file reached without traversing a symlink, and be at most 64 KiB.
Do not use a heredoc, $(...), or an inline multi-line --body. All three
are refused before execution on a poller turn, and a refused submission is how a
completed review silently fails to reach GitHub. Write the body to the scratch
file and pass --body-file: because the file is read during authorization, $,
backticks and multi-line text need no escaping at all.
6. Confirm submission
After the gh pr review call completes:
gh pr view <num> --repo jasoncarreira/mimir --json reviews
Read the last entry of reviews from the returned JSON.
Verify your login (mimir-carreira) appears in the reviews list.
7. Operator ping (non-mimir-carreira PRs)
For PRs authored by anyone other than mimir-carreira, fulfill commitment
c-5494103062: send a message to the operator channel noting the PR number
and your verdict.
Non-negotiable rules
Always call
gh pr reviewbefore ending the turn. If the turn ends with a drafted review in the output and nogh pr reviewcall in the tool sequence, the review was not submitted.A failing tool call mid-review does not abort submission. Recover: note the failure in the review body, then call
gh pr reviewanyway with what you have.Budget exhaustion is not allowed to eat the submission step. After the verdict is known,
gh pr reviewplus confirmation outranks optional extra reads, broad test runs, or repeated async-job polling. When near the tool-call ceiling, submit first with the evidence already gathered.Never use only turn output as the delivery mechanism. The operator reads GitHub, not
turns.jsonl.
Poller-triggered reviews
When this skill fires from poller:github-activity, the trigger includes
the PR number and repo. There is no human watching the turn output — the
only way the review reaches the operator (and other contributors) is via
gh pr review. Apply the same steps above; the poller context changes
nothing about the submission requirement.
Common failure modes
| Symptom | Fix |
|---|---|
Read(tests/foo.py) -> "file does not exist" |
Skip Read, continue from diff, still submit |
gh pr diff <num> -> HTTP 406 "diff too large" |
Use gh pr view <num> --repo <owner>/<repo> --json files (gh api is not admitted) |
gh pr review exits non-zero |
Check --help; verify PR is open; retry once |
Review body contains backticks / ${} |
Nothing special — --body-file reads the file directly, so no shell expansion occurs |