| name | review |
| description | Run a reviewer subagent against uncommitted local changes, a named branch, or a GitHub PR. Local and branch modes write a review file plus a summary to disk. PR mode posts the findings as a PENDING GitHub review for the user to inspect and submit through the UI. |
| when-to-use | Use when asked to 'review', 'code review', 'review my changes', 'review this PR', or '/review'. |
| argument-hint | [--local | --branch <name> | --pr <number-or-url> | <auto-detect>] |
Review Skill
You are an orchestrator that runs a reviewer subagent against one of three review targets. You coordinate only — all review findings are authored by a subagent whose prompt is seeded with the reviewer persona instructions, never by the orchestrator directly.
Persona Injection
This skill uses the reviewer persona. The persona instructions are defined at:
<dirname of this SKILL.md>/../shared/personas/reviewer.md
Resolve this path once at the start of the run (the system context gives you the absolute path to this SKILL.md). Read the file with read_file and store its contents as reviewer_persona_instructions.
When launching the reviewer subagent, prepend the persona instructions to the prompt. Do NOT pass a persona parameter to spawn_subagent — that parameter is not supported. Instead, prefix the description with [reviewer] so the pager's subagent label renderer surfaces "Reviewer" at the top of the subagent row (see Step 2 below).
- Local mode (default) -- uncommitted local changes (staged + unstaged + untracked).
- Branch mode -- the diff between a named branch and its merge-base with the default base branch.
- PR mode -- a GitHub pull request. Findings are posted as a PENDING review for the user to inspect and submit through GitHub.
The reviewer subagent is read-only -- it never modifies code. The orchestrator never edits source either; the only artifacts produced are a review file, a summary file, and (in PR mode) a pending GitHub review.
Invocation
The user runs:
/review # local mode (default)
/review --local # local mode (explicit)
/review --branch <name> # branch mode (explicit)
/review --pr <number-or-url> # PR mode (explicit)
/review <plain-arg> # auto-detect; see disambiguation below
Argument parsing
Parse the argument string with these deterministic rules, applied in order. The first rule that matches wins; do not fall through.
- Empty / whitespace-only:
MODE=local, no target.
- Starts with
--local: MODE=local. Reject any extra positional argument with an error.
- Starts with
--branch <name>: MODE=branch, TARGET=<name>. The branch name is required -- if the flag appears with no following token (or only with another ---prefixed token), reject with Flag --branch requires an argument: <branch-name> and stop.
- Starts with
--pr <id-or-url>: MODE=pr, TARGET=<id-or-url>. The id or URL is required -- if the flag appears with no following token (or only with another ---prefixed token), reject with Flag --pr requires an argument: <number-or-url> and stop.
- Starts with
-- but does not match any of the above: reject with Unknown flag: <flag>. Valid flags: --local, --branch <name>, --pr <number-or-url>. and stop. Do NOT fall through to auto-detect.
- Plain argument given (no flag prefix): auto-detect against the rules below, also applied in order:
- Matches the regex
^https?://github\.com/[^/]+/[^/]+/pull/\d+(?:[/?#].*)?$ -- treat as PR URL. MODE=pr, TARGET=<url>.
- Matches
^#?\d+$ (optional leading #, then pure digits) -- treat as PR number. MODE=pr, TARGET=<digits without leading #>.
- Resolves to a local or remote branch via
git rev-parse --verify --quiet <arg> or git rev-parse --verify --quiet origin/<arg> -- treat as branch. MODE=branch, TARGET=<arg> (use the bare name, not origin/<arg>).
- None of the above -- ask the user whether the argument is a PR identifier or a branch name (use the appropriate ask/question tool if available). Provide three options: "PR (treat as PR identifier)", "Branch (treat as branch name)", and "Cancel". On Cancel, stop.
If the user passes both a flag and a positional argument (e.g., /review --local somebranch), reject with a clear error message and stop. The flags are mutually exclusive.
Setup
Generate a unique ID for this run's artifact files. Execute this via run_terminal_cmd and capture stdout:
python3 -c "import uuid; print(uuid.uuid4().hex[:8])"
This matches the pattern used by implement/SKILL.md. The previous draft of this skill chained two fallbacks (/proc/sys/kernel/random/uuid and date +%s), but the /proc path is missing on macOS and the date fallback's tail -c 9 kept a trailing newline -- both bugs. python3 is reliably present in the supported environments; if it is genuinely absent, the validation step below catches it with a clear error.
Validate that the command produced a non-empty 8-character string. If REVIEW_ID is empty or the command failed, report the error to the user (with the suggestion to install Python 3) and stop -- do not proceed with empty/malformed file paths.
Store the output as REVIEW_ID. Set a restrictive umask first so all subsequent artifact writes land at mode 0600 -- the diff and review files can capture .env snippets or other secrets and the default 0644 leaks them to other users on shared hosts:
umask 077
Then define the file paths used throughout the run:
summary_file: /tmp/grok-review-summary-${REVIEW_ID}.md (orchestrator-written; used in local and branch modes only -- not written or read in PR mode)
review_file: /tmp/grok-review-${REVIEW_ID}.md (reviewer subagent writes here; produced in all modes)
diff_file: /tmp/grok-review-diff-${REVIEW_ID}.diff (collected diff fed to the reviewer; produced in all modes)
pending_review_payload: /tmp/grok-review-pending-${REVIEW_ID}.json (PR mode only -- not written or read in local/branch modes, so cleanup of this path is a no-op outside PR mode)
Initialize state variables:
mode: one of local, branch, pr (set by argument parsing).
target: the branch name, PR number, or PR URL (empty in local mode).
head_sha, base_sha, owner, repo, pr_number, pr_url, pr_title (populated in PR mode by Step 1).
changed_files: list of file paths in the diff (populated by Step 1).
Step 1: Resolve target & collect diff
The diff collection commands differ per mode.
Local mode
-
Detect changes:
git status --porcelain
If the output is empty, print "No local changes to review (working tree clean)." Skip directly to Step 4 (Cleanup -- use the local/branch sub-case; both <diff_file> and the helper file list will be no-op rm -f since neither was written yet) and then Step 5 (Final report). The Final report should use the "Local / branch (empty-diff exit)" bullet. Do NOT launch the reviewer.
-
Build a unified diff covering staged + unstaged tracked changes AND untracked files:
# Staged + unstaged tracked changes (includes deletions and modifications).
# Guard against fresh `git init` repos with no commits -- `git diff HEAD`
# fails there with "ambiguous argument 'HEAD'". In that case, leave the
# tracked-change portion empty and let the untracked loop populate the file.
# `core.quotepath=false` keeps non-ASCII / space-bearing paths unquoted so
# the parser in Step 3 PR mode sees literal paths.
if git rev-parse --verify --quiet HEAD >/dev/null; then
git -c core.quotepath=false diff HEAD > "${diff_file}"
else
: > "${diff_file}"
fi
# Append each untracked file as an added-file diff (skip ignored files)
git ls-files --others --exclude-standard -z | while IFS= read -r -d '' f; do
git -c core.quotepath=false diff --no-index -- /dev/null "$f" >> "${diff_file}" || true
done
# Size check: print the byte count so the orchestrator can gate continuation.
# See the executable size check handling immediately below.
wc -c "${diff_file}"
Trade-off note: git diff --no-index exits with status 1 when differences are present (which is always, here), so we suppress its exit with || true. The alternative -- git add -N <untracked> && git diff HEAD && git rm --cached <untracked> -- mutates the index and risks leaving the user in an unexpected state if interrupted; the --no-index approach is non-mutating, which is why this block uses it.
Fresh-repo guard: on a brand-new git init with zero commits there is no HEAD to diff against, so the if git rev-parse --verify --quiet HEAD branch above falls through to the else and ${diff_file} starts empty. Everything in the working tree at that point is untracked from git's perspective, so the subsequent git ls-files --others loop captures it correctly. The rest of the local-mode flow is unchanged.
Size gate (orchestrator-side): read the byte count emitted by wc -c and act on it:
- : abort with an error telling the user to add the offending paths to (point them at plus to find the worst offenders -- typical culprits are an untracked , , , or a stray dataset). Do NOT launch the reviewer; run cleanup and stop.
Branch mode
-
Determine the base branch. Try in order:
if git rev-parse --verify --quiet origin/main >/dev/null; then
BASE=origin/main
elif git rev-parse --verify --quiet origin/master >/dev/null; then
BASE=origin/master
else
BASE=""
fi
Use an explicit if/elif/else (not two unconditional && lines) so that origin/master does not overwrite origin/main when both exist (which happens during master-to-main migrations and on mirror repos). The >/dev/null redirect prevents the SHA emitted by git rev-parse from leaking into the orchestrator's captured output.
If BASE is empty (neither ref exists), ask the user which base ref to compare against (use the appropriate ask/question tool if available) (offer the local default branch name(s) you can detect via git symbolic-ref refs/remotes/origin/HEAD, plus an "Other" option).
-
Verify the target branch exists:
git rev-parse --verify --quiet "${target}" || git rev-parse --verify --quiet "origin/${target}"
If neither resolves, report the error and stop.
-
Compute the merge base and collect the diff (core.quotepath=false prevents C-style path quoting so the parser in Step 3 PR mode sees literal paths -- gh pr diff does not quote paths, so PR mode is unaffected):
MERGE_BASE=$(git merge-base "${BASE}" "${target}")
git -c core.quotepath=false diff "${MERGE_BASE}".."${target}" > "${diff_file}"
git -c core.quotepath=false diff --name-only "${MERGE_BASE}".."${target}" > /tmp/grok-review-files-${REVIEW_ID}.txt
-
Empty-diff handling: if ${diff_file} is empty (or contains only whitespace), print "Branch ${target} has no changes vs ${BASE}." Skip directly to Step 4 (Cleanup -- use the local/branch sub-case) and then Step 5 (Final report). The Final report should use the "Local / branch (empty-diff exit)" bullet to note that the branch has no changes vs its base. Do NOT launch the reviewer.
Read changed_files from the names file.
PR mode
-
Verify gh authentication:
gh auth status
If this exits non-zero, warn the user that the PR cannot be fetched without gh auth, then stop with instructions to run gh auth login.
-
Fetch PR metadata in a single round trip (works for both numeric IDs and full URLs). Note that gh pr view --json does NOT expose a baseRepository field (verified: gh pr view 1 --json baseRepository returns Unknown JSON field: "baseRepository". The available repo fields are headRepository, headRepositoryOwner, and isCrossRepository). The owner/repo for the upstream where the PR lives must therefore be derived from the url field instead. The files field is also requested here so we do not need a second round trip:
gh pr view "${target}" --json number,title,body,headRefOid,baseRefOid,headRefName,baseRefName,url,headRepository,headRepositoryOwner,isCrossRepository,files \
> /tmp/grok-review-prmeta-${REVIEW_ID}.json
Parse the JSON and populate:
pr_number from .number
pr_title from .title
pr_url from .url
head_sha from .headRefOid
base_sha from .baseRefOid
owner, repo -- parse from pr_url using the regex ^https?://github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/pull/\d+. The URL always points at the upstream where the PR lives, even for cross-repo PRs (isCrossRepository: true), so this is the correct source for the review-posting endpoint.
changed_files -- extract via jq -r '.files[].path' /tmp/grok-review-prmeta-${REVIEW_ID}.json > /tmp/grok-review-files-${REVIEW_ID}.txt, then read the file.
Validate that all required fields are non-empty: pr_number, head_sha, base_sha, owner, repo. If any is missing or empty (which can happen for very old PRs, PRs in unusual states, or partial responses), surface the parsed JSON to the user and stop -- do not proceed to build a payload with fields.
After Step 1, report progress: "Collected diff for target
. Launching reviewer..."
Step 2: Launch reviewer subagent
Launch a single reviewer subagent by calling spawn_subagent. Emit the spawn_subagent tool call before producing any "reviewer is starting" narration; the post-launch progress message ("Review complete. Processing findings...") belongs in a later assistant message after the tool result is in hand.
spawn_subagent parameters:
subagent_type: "general-purpose"
description: "[reviewer] <mode> <target-summary>" (e.g., "[reviewer] pr #4221" or "[reviewer] branch feature/foo" or "[reviewer] local changes"). The [reviewer] prefix is parsed by the pager's subagent label renderer (see format_subagent_label in xai-grok-pager) so the subagent row shows "Reviewer" instead of the generic "General" fallback. The bracketed prefix is stripped from the displayed description.
Build the prompt with the mode-specific context. Prepend the reviewer persona instructions (loaded during setup) to the prompt. Use this template:
<reviewer_persona_instructions>
---
You are reviewing code changes. Mode: <mode>.
Target: <target-summary-line>
<if PR mode: PR URL: <pr_url>>
<if PR mode: head SHA: <head_sha>, base SHA: <base_sha>>
<if branch mode: base: <BASE>, merge-base: <MERGE_BASE>, head: <target>>
The unified diff is at: <diff_file>
The list of changed files is at: /tmp/grok-review-files-${REVIEW_ID}.txt
Read the diff first to understand the scope. The diff alone is often not enough
context, so you should also `read_file` the source files referenced in the diff
to understand call sites, types, and surrounding logic before flagging issues.
Write your structured findings to: <review_file>
Format:
## Summary
<2 to 4 sentence overall assessment of the changes -- what they do, whether
they look correct, the dominant risk areas. This goes at the very top of the
file, before any individual issues.>
## Issues
### Issue 1 -- Severity: bug
- File: path/to/file.ext:LINE
- Description: <what is wrong>
- Suggestion: <how to fix>
- Status: open
### Issue 2 -- Severity: suggestion
- File: path/to/file.ext:LINE
- Description: ...
- Suggestion: ...
- Status: open
Severity must be one of: bug, suggestion, nit. Each issue's Status field must be set to "open" (as shown in the example above).
<if PR mode, include this paragraph verbatim:>
IMPORTANT: For each issue, the File line MUST reference a single line number on
the RIGHT side of the diff (the line number in the new/post-change file, not
the pre-change file). If a finding spans a range, pick the most representative
single line on the RIGHT side. This requirement is mandatory because the
orchestrator will post these findings as inline comments on the GitHub PR, and
the GitHub API rejects comments that do not target a line present in the diff.
<end if>
If the diff is genuinely fine and you have no issues, write the Summary and an
empty `## Issues` section (or omit the Issues section entirely). Do not invent
issues to fill space.