review-that-runs
Skill32 starsChanged 8 months agoMITView on GitHub
Review a pull request by running it, not just reading it. Detects the project's forge (GitHub, GitLab, Bitbucket, Azure DevOps, Gerrit, plain git) and stack, checks out the change in an isolated worktree, runs the project's own CI gates, drives the app in Chrome for frontend changes, verifies every finding before writing it, and writes the review to a local notes file with one proof line per finding, then removes the worktree and every container it started. Posts nothing to the code host. Use when asked to review a PR, MR, change or branch, locally or before posting.
- Installs packages
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- adr-setupSkill
- capability-mapSkill
- privacy-assessment-railsSkill
- privacy-by-design-railsSkill
- privacy-review-railsSkill
- sherlockSkill
- template-skillSkill
- threat-modelSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 6 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 48
Target: read the PR's metadata and diff with the forge's own tool, and fetch its head.
- Distinctive: line 51
Big picture: read CLAUDE.md / AGENTS.md, the project's docs and ADRs, and the modules around the change.
- Distinctive: line 56
Verify: test each finding before writing it up.
- Distinctive: line 59
Check anchors: every anchor must land on a line the PR diff shows.
- Very specific: line 68
Gates: read the CI config first (.github/workflows/, .gitlab-ci.yml, bitbucket-pipelines.yml, Jenkinsfile...).
- Distinctive: line 100
Run the project's own gates, the ones CI runs.
- Very specific: line 103
No backend? Use the project's own mock layer (MSW handlers, fixtures, cassettes, factories) instead of starting services against the user's …
- Very specific: line 136
Keep the notes file and <state>/profile.md.
- Distinctive: line 210
When the notes are posted, the review body ends with this line, verbatim:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Review that runs
Written for the post Six weeks of AI code review: what it caught and what it let through, which tells what these rules caught and missed over 79 real PRs.
A first-pass PR review for a human to read, cut and post. The agent checks; the human decides. Nothing here approves or blocks a PR.
Structure borrowed from Matteo Collina's reviewing-prs.md (skill nodejs-core,
https://github.com/mcollina/skills). The difference: this one executes.
Hard rules
- Verify before you claim. Anything you cannot prove against the code on the branch does not become a comment. A killed false positive is the system working.
- Never touch the user's working tree. Fetch the PR and
git worktree add --detachinto<state>/<pr|mr|change>-<N>/worktree/(see Output template), never inside the repo. - Nothing reaches the code host. No
gh pr review,glab mr note,az repos prcomment etc. The deliverable is the notes file. Posting it is the user's call. - Detect, don't assume. Forge, language, package manager and gates come from the repository, never from habit. A guessed
npm testthat fails in a Ruby repo gets blamed on the PR. - Frontend changes get run in Chrome. The real app, styled, built from the PR's own worktree. A passing test, a screenshot of unstyled markup and a careful read of the diff are not validation: layout and overflow are invisible to all three. If the app genuinely cannot run, say so and name the reason.
- Every finding ends with a proof line.
*Verified:*when you tested it,*Inferred:*when you only read it. No proof line, no comment. - Mark what you changed to make it run. Any edit to mocks, fixtures or env to boot
the app carries a
// REVIEW SCRATCH: not part of the PRcomment, and the overview names it, so mock behaviour is never read as the PR's. - Leave nothing running. The worktree, every container, volume, network, dev server and Chrome page the review started is torn down before the notes path is reported, also when the review is aborted or fails halfway (see Cleaning up). Only the notes file and the profile survive a review.
Flow
Track progress in a todo list with exactly these steps, in order:
- Detect: identify the forge, then fetch the head and build the project profile (below) before running anything.
- Target: read the PR's metadata and diff with the forge's own tool, and fetch its head. Note the head SHA and the base branch.
- Spec: find the ticket in the branch, title or description. Unreachable? The PR description is the spec. Say which one you used.
- Big picture: read
CLAUDE.md/AGENTS.md, the project's docs and ADRs, and the modules around the change. A diff can be locally correct and still wrong for the system. - What changed: one business-level paragraph. "Fixes the duplicate image on the product page", never "renamed a to b and added an if".
- Scope check: the spec against the diff. Matches, misses pieces, or does things nobody asked for.
- Find problems: correctness (edge cases, wrong data, unhandled errors, races) and design (consistency with sibling code, reinventing what already exists, silent breaking changes). Open every changed file at the PR's revision. When a function, route or event changes, find its other callers including the ones the diff does not touch.
- Verify: test each finding before writing it up. Prefer a failing test that reproduces it. Frontend goes through Chrome (below). Discard what does not hold.
- Write: fill the template.
- Tighten: cut filler, restated findings and praise adjectives.
- Check anchors: every anchor must land on a line the PR diff shows.
- Clean up: tear down everything the review started (below), then report the notes path.
Detecting the project
The forge comes from the remote and decides how to fetch. Everything else comes from the PR's head, since a PR can change the lockfile or CI config.
- Forge: from
git remote get-url origin. Decides how to fetch the head (pull/<N>/head,merge-requests/<N>/head,refs/changes/...), the name of the thing (PR, MR, change), anchor format and suggestion syntax. Self-hosted and unrecognised? Ask. CLI missing or logged out? Fall back to plaingit fetchand ask for the description. - Stack: manifests and lockfiles name the language, package manager, toolchain pins and frozen-install command. Monorepo? Note which packages the diff touches.
- Gates: read the CI config first (
.github/workflows/,.gitlab-ci.yml,bitbucket-pipelines.yml,Jenkinsfile...). What CI runs is what will judge the PR.Makefile,bin/,script/andpackage.jsonscripts usually wrap the same steps. - Runtime: how the app boots (compose,
Procfile.dev,bin/dev), what services it needs, which mock layer exists, and whether the diff renders in a browser.
| Forge | Metadata and diff | Fetch the head |
| GitHub | gh pr view <N> --json title,body,headRefOid,baseRefName, gh pr diff <N> | git fetch origin pull/<N>/head |
| GitLab | glab mr view <N> --output json (sha, target_branch), glab mr diff <N> | git fetch origin merge-requests/<N>/head |
| Gitea / Forgejo | REST /api/v1/repos/<owner>/<repo>/pulls/<N> and .diff | git fetch origin pull/<N>/head |
| Bitbucket Cloud | REST /2.0/repositories/<ws>/<repo>/pullrequests/<N> and /diff | The source branch; for a fork, from the fork's URL |
| Azure DevOps | az repos pr show --id <N> (lastMergeSourceCommit) | sourceRefName; refs/pull/<N>/merge is a merge preview, not the head |
| Gerrit | ssh -p 29418 <host> gerrit query --current-patch-set change:<N> | git fetch origin refs/changes/<NN>/<N>/<patchset>, NN = last two digits |
| Plain git | The user names the branch and its base | git fetch origin <branch> |
Diff against the merge base (git diff <base>...<head>, three dots). Two dots against a moved base shows other people's commits as the PR's.
| Lockfile | Frozen install |
pnpm-lock.yaml / yarn.lock / package-lock.json / bun.lock | pnpm i --frozen-lockfile / yarn install --immutable (v1: --frozen-lockfile) / npm ci / bun i --frozen-lockfile |
Gemfile.lock | BUNDLE_FROZEN=true bundle install |
uv.lock / poetry.lock / Pipfile.lock | uv sync --frozen / poetry sync / pipenv sync |
go.sum / Cargo.lock | go mod download / cargo fetch --locked |
composer.lock / mix.lock | composer install / mix deps.get |
mvnw / gradlew / packages.lock.json | the wrapper, never a global binary / dotnet restore --locked-mode |
Toolchain pins to copy: .tool-versions, mise.toml, .nvmrc, .node-version, .ruby-version, .python-version, rust-toolchain.toml, global.json, the packageManager field. A CI step that needs secrets or deploy access does not run locally: list it under what could not be checked. Monorepo (pnpm-workspace.yaml, turbo.json, nx.json, Cargo or Go workspaces): run the gates for the changed packages and their dependents.
Save the profile to <state>/profile.md (see Output template) with the head SHA it was built from. Reuse it on the next review unless a lockfile, toolchain pin or CI file changed since that SHA. Copy it as the first line of the validation run, e.g. "GitLab MR, Rails 7 + Hotwire, Ruby 3.3, gates from .gitlab-ci.yml: rspec, rubocop, brakeman."
Running the PR
- Copy the project's toolchain pins into the worktree and install with the lockfile frozen, using the command the profile names.
- Run the project's own gates, the ones CI runs. Report what ran and what did not.
- Serve the frontend on a port the user is not using. Their dev server is serving their branch, not the PR's.
- No backend? Use the project's own mock layer (MSW handlers, fixtures, cassettes,
factories) instead of starting services against the user's local database. Compose
stacks run under their own project name (
-p review-<N>) and ports, or not at all. Seed what the changed flow needs, and mark it (rule 7). - Name everything you start after the review, so the cleanup can find it without guessing:
compose with
-p review-<N>,docker runwith--name review-<N>-<service>and--label review-that-runs=<N>, test databases and schemas asreview_<N>. Never reuse a container, volume or database the user already has. Anything not in Docker (dev server, test runner, watcher, tunnel) starts in its own process group and is written to
<state>/<pr|mr|change>-<N>/pidsthe moment it is up: one line per process,<pgid> <port or -> <command>. Memory of what was started does not survive a crashed or interrupted session; the file does.setsid pnpm dev --port 4173 > "$STATE/pr-<N>/dev-server.log" 2>&1 & echo "$! 4173 pnpm dev --port 4173" >> "$STATE/pr-<N>/pids"
Cleaning up
Runs as the last step, and also when the review aborts, errors out or is interrupted by the user: do the teardown first, then explain what went wrong. Nothing waits for the user to ask.
| Started | Teardown |
| Worktree | git -C <repo> worktree remove --force <state>/<pr|mr|change>-<N>/worktree, then git -C <repo> worktree prune |
| Compose stack | docker compose -p review-<N> down --volumes --remove-orphans |
| Standalone containers | docker rm -f $(docker ps -aq --filter label=review-that-runs=<N>), then the same docker volume ls / docker network ls filter |
| Dev server, test runner, watcher | For each line in <state>/<pr|mr|change>-<N>/pids: kill -TERM -- -<pgid>, kill -KILL after a few seconds if still alive; then lsof -i :<port> for every port listed, kill anything left, and delete the file |
| Chrome pages | close_page on every page the review opened; leave the user's tabs alone |
| Test database or schema | Drop review_<N> on the service the review itself started; never on the user's |
Installed toolchains, caches, node_modules | Live inside the worktree and go with it; nothing is installed globally for a review |
Keep the notes file and <state>/profile.md. Then check: git -C <repo> worktree list
shows only the user's worktrees, docker ps -a --filter label=review-that-runs=<N> and
docker compose -p review-<N> ps are empty, the pids file is gone and every port it
listed is free. A pids file found at the start of a review is a previous run that did
not get this far: tear it down first. If a
teardown step fails, the validation run names what was left and the exact command to
remove it.
Frontend validation with Chrome DevTools MCP
Server-rendered templates (ERB, Jinja, Blade, HEEx...) count as frontend.
new_pageon the served app, log in, navigate to the changed flow.take_snapshotto find elements; screenshots are evidence, not discovery.- Measure layout with
evaluate_scriptover the real DOM:innerWidth, the container'sclientWidth, the table'sscrollWidth. State the viewport you measured at, andresize_pagewhen the finding is about a narrower screen. - Try the change as each affected role, including one that should not see it.
list_console_messagesat the end. New errors are findings.
Severity
Open each comment with its severity as a plain sentence:
| Tier | Lead-in |
| Blocker | "This breaks prod." / "This loses data." / "This is a security hole." |
| Should-fix | "This is a real bug." |
| Suggestion | "Worth considering." |
| Nitpick | "Small thing." |
Output template
Write the notes to <state>/<pr|mr|change>-<N>/review-notes.md, using the forge's term, where <state> is ${XDG_STATE_HOME:-$HOME/.local/state}/review-that-runs/<host>/<path>, the host and path of the origin URL without .git (a local remote has no host: use its path alone). Nothing the review writes goes inside the reviewed repo; tell the user the notes path at the end:
# <PR|MR|Change> #<N> review notes (<forge>)
## Overview
<Business-level description, 2-5 sentences. Name any REVIEW SCRATCH edits.>
*Verdict: <does what was asked / misses X / also does unrelated Y>*
## Validation run
<The project profile. Then one line per pass that actually ran, one line for what
could not be checked and why, and one line naming what was torn down, or what was left
behind and the command that removes it.>
## Comments
<!-- anchor path="path/to/file.ext" start_line=N line=M side=RIGHT -->
<Lead-in. One short paragraph: what breaks and what it costs. At most one evidence
block: a suggestion, a diff, or input -> expected vs got.>
*Verified: <how you confirmed it>*
---
<!-- anchor path="path/to/other.ext" line=N side=RIGHT -->
<next comment>
*Inferred: <what you read, and why you could not test it>*Line numbers are head-branch numbers on lines the diff shows. side=RIGHT is GitHub's
term; on other forges it is the new-file line. Use the forge's suggestion syntax (```suggestion on GitHub, ```suggestion:-0+0 on GitLab, a plain ```diff
elsewhere) only when the fix is exactly the anchored lines rewritten: Apply replaces the
whole anchored range. When posted, the review goes out as a plain comment, with no
approval, vote or change request. Those are a human's call.
When the notes are posted, the review body ends with this line, verbatim:
First pass by review-that-runs. Approving is a human's call.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

