Review loop skill

Drive every reviewer thread on a GitHub pull request to zero unresolved in bounded iterations.

by Parcha-ai·MIT license·GitHub ↗

★ 62 Stars on the repo·Checked

npx degit Parcha-ai/parcha-skills/review-loop/skills/review-loop#main ~/.claude/skills/review-loop

SKILL.md · 10.0 KB · installs the whole folder to ~/.claude/skills/review-loop

Files of Review loop

Files 1 file
Show the full text183 lines

Review loop

This skill has one job: take a GitHub PR with open review threads and drive every thread, from every reviewer, to zero unresolved, in a bounded number of iterations, then report what it did in a fixed format. It does not QA the running application; that is autoqa's job. It does not decide whether the diff is safe beyond what reviewers raised; that is blast-radius's job. It does not merge, and it does not mint credentials.

Inputs

Input Required Default
Repository (owner/repo) no The origin remote of the current checkout
PR number no The PR for the current branch (gh pr view)
Target (open-for-review, review-clean, or merge-ready) no review-clean
Trigger comment text no @greptile-apps review
--max-iterations N no 3
Gate command no None. The caller or the repository supplies the command that runs tests and lint (for example the repo's make check); the skill only says where in the loop it runs
Base-branch sync no None. The caller or repository supplies the rebase or merge policy; the skill says where it runs

Auth precondition: the caller has authenticated gh before invoking this skill. The skill never mints, reads, or stores tokens. If gh auth status fails, stop and report it.

Iteration

Repeat at most --max-iterations times; the default is 3, and a caller raises it only for a stated reason. Each iteration:

  1. Sync with the base branch if the caller supplied a sync policy. Run the gate command when the head changed or no exact-head result exists. Reuse valid exact-head evidence. A red gate stops the iteration; fix the gate before touching review comments.
  2. Fetch every open thread. See "Fetching everything". Do not filter to one reviewer.
  3. Triage by content, not by author. For each thread decide: actionable (a code change is needed), informational (no change, reply explains why), or false positive (reply explains why). Record the decision. A reply that proves inherited behavior or base parity without a code push keeps prior exact-head evidence valid. Do not rerun gates or restart proof and description work.
  4. Fix every actionable item in one pass. Read the file at the cited line, understand the comment in context, make the change.
  5. If code changed, run the gate command again. Do not push a red gate.
  6. If code changed, commit and push. Commit message names the iteration, for example Address review feedback (review-loop iteration 2).
  7. Reply and resolve. See "Human threads" and "Bot threads". A reply goes on the thread that raised the point, not in a new top-level comment.
  8. Re-request review once per push. A reply without a push does not require another request. See "Greptile specifics" and "Devin specifics". Wait for the results with a bounded poll.
  9. Check the target. For review-clean or merge-ready, require zero unresolved threads across every reviewer and, when Greptile is installed, a 5/5 score from its latest summary. For open-for-review, require a non-draft PR and focused proof. Automated review may remain pending.

If the cap is reached with threads still open, stop and report them; do not start another iteration.

Fetching everything

Fetch all three comment sources on every iteration; a reviewer can use any of them.

# Inline review threads with resolution state (GraphQL; paginate on endCursor)
gh api graphql -f query='...' # see references/graphql-queries.md

# Reviews (approve, request changes, comment) with bodies
gh api --paginate "repos/{owner}/{repo}/pulls/<PR_NUMBER>/reviews?per_page=100"

# Issue comments on the PR conversation tab (Greptile summaries, Devin notes, humans)
gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100"

A thread is open when isResolved is false. A review body or issue comment is a thread when it asks for a change or a reply; treat it as open until it is answered. Bot summaries that edit in place (Greptile does this) must be read by updated_at, not created_at.

Human threads

  • Reply on the thread with what was done and why, citing the commit. When nothing was changed, say what was considered and why the code stays as is.
  • Never resolve a thread a human opened without replying first. Prefer to leave resolution of human threads to the human unless the repository's policy says the author resolves.
  • Never dismiss a human review. A "changes requested" review is cleared by the reviewer re-reviewing, and re-requesting review is the only action the loop takes on it.

Bot threads

Resolve a bot thread after its item is fixed or answered. Use the GraphQL resolveReviewThread mutation, batched with aliases, and assert the result:

gh api graphql -f query='
mutation {
  t1: resolveReviewThread(input: {threadId: "ID1"}) { thread { isResolved } }
  t2: resolveReviewThread(input: {threadId: "ID2"}) { thread { isResolved } }
}'

Every alias in the response must report isResolved: true. If one does not, re-fetch the thread and treat it as still open; do not count it as resolved. Batch up to 20 aliases per request. Full queries are in references/graphql-queries.md.

Resolving is only the GraphQL resolveReviewThread mutation. A REST reply (the pulls/{pr}/comments/{id}/replies endpoint) answers a thread but never resolves it, and a replied-but-unresolved thread still fails the zero-unresolved exit criterion. Always resolve through GraphQL, never assume a reply resolved anything.

Greptile specifics

  • Before posting a trigger comment, check for a run already in progress. Look at gh pr checks <PR_NUMBER> --json name,state and at repos/{owner}/{repo}/commits/<HEAD_SHA>/check-runs for a check whose name matches greptile (case-insensitive). If its state is PENDING or IN_PROGRESS, do not post; wait for it.
  • The trigger comment text is configurable; the default is @greptile-apps review. Post it at most once per push.
  • Poll the check run at 10-second intervals for up to 10 minutes. A check run that has only just appeared but is still PENDING or IN_PROGRESS is not a result: wait for a terminal conclusion AND for the summary comment's updated_at to move past the trigger time before reading a score. A freshly-appeared check run beside an unchanged summary means Greptile has started but not finished, and reading the old summary then records a stale score.
  • On large PRs the tagged review may never create a check run for the new head; Greptile instead edits its existing summary comment. If no check run appears after a few attempts, poll the most recently updated Greptile issue comment and stop when its updated_at is later than the trigger comment and its body carries a score.
  • Read the score from three places and use the most recently updated one: the most recently updated Greptile-authored issue comment (it edits in place, so sort by updated_at), the PR body, and the latest review from greptile-apps[bot] or greptile-apps-staging[bot]. The score looks like 3/5, 5/5, or Confidence: 3/5.
  • Freshness gate: a score counts only for the current head. Resolve the commit it was produced for (the Greptile check run's head_sha, or a summary whose updated_at post-dates the head commit) and compare it to the PR's current head SHA. A score from an earlier commit is stale: re-trigger, wait, and never record it as the exit criterion.
  • Carry forward the items under "Prompt to fix all with AI" in the Greptile summary comment, even when the inline comment endpoint returns zero unresolved comments. They count as open until fixed or answered.
  • For review-clean or merge-ready, Greptile requires 5/5 and zero unresolved Greptile threads.

Devin specifics

  • Devin reviews when the PR becomes ready for review. A draft PR never gets a Devin review. If the PR is a draft and the caller wants Devin's review, mark it ready (gh pr ready) and say so in the report.
  • Devin posts inline threads and a summary issue comment. Triage them like any other thread; reply and resolve through the same GraphQL path.
  • Devin may report more issues than it posts ("N issues, M not posted by your settings"). The flags hidden by GitHub settings are not fetchable through the API and are not review threads: do not count them toward unresolved threads. Record them in the report as "M Devin flags not visible via API (see the Devin dashboard)" for a human, but the exit criterion is over the threads that actually exist on the PR.
  • Re-requesting Devin is a push; there is no trigger comment.

Stop on timeout

If a poll for review results times out, stop the loop and report the timeout. Never continue with stale or missing review results, and never count a thread as resolved because the reviewer did not answer.

Report

End with this table. Print it even when the loop stops early.

Field Value
Repository / PR owner/repo#N
Iterations N of max M
Threads found (per reviewer) Greptile N, Devin N, humans N
Resolved this run N
Replied, left open for a human N
Remaining N, then one line each: path:line and a short quote
Final Greptile score X/5, or "not installed"
Stop reason exit criterion met, max iterations, timeout, red gate, auth failure

Write the report and every reply through unslop before posting.

1---
2name: review-loop
3description: Drive every reviewer thread on a GitHub pull request to zero unresolved in bounded iterations. Covers bot reviewers such as Greptile and Devin and human reviewers. Fetches every inline comment, review, and issue comment, fixes what is actionable, replies, resolves bot threads through GraphQL, re-requests review once per push, and ends with a fixed-format report. Use when the user says "review loop", "address the review comments", "get this PR to zero unresolved", "greploop", or "make Greptile happy".
4license: MIT
5compatibility: Requires git and an authenticated gh CLI. Greptile and Devin sections apply only when those apps are installed on the repository.
6metadata:
7 upstream: greptileai/skills (greploop, greploop-apps)
8 vendored: 2026-09-15
9---
10 
11# Review loop
12 
13This skill has one job: take a GitHub PR with open review threads and drive every thread, from
14every reviewer, to zero unresolved, in a bounded number of iterations, then report what it did
15in a fixed format. It does not QA the running application; that is `autoqa`'s job. It does not
16decide whether the diff is safe beyond what reviewers raised; that is `blast-radius`'s job. It
17does not merge, and it does not mint credentials.
18 
19## Inputs
20 
21| Input | Required | Default |
22|---|---|---|
23| Repository (`owner/repo`) | no | The `origin` remote of the current checkout |
24| PR number | no | The PR for the current branch (`gh pr view`) |
25| Target (`open-for-review`, `review-clean`, or `merge-ready`) | no | `review-clean` |
26| Trigger comment text | no | `@greptile-apps review` |
27| `--max-iterations N` | no | 3 |
28| Gate command | no | None. The caller or the repository supplies the command that runs tests and lint (for example the repo's `make check`); the skill only says where in the loop it runs |
29| Base-branch sync | no | None. The caller or repository supplies the rebase or merge policy; the skill says where it runs |
30 
31Auth precondition: the caller has authenticated `gh` before invoking this skill. The skill
32never mints, reads, or stores tokens. If `gh auth status` fails, stop and report it.
33 
34## Iteration
35 
36Repeat at most `--max-iterations` times; the default is 3, and a caller raises it only for a
37stated reason. Each iteration:
38 
391. Sync with the base branch if the caller supplied a sync policy. Run the gate command when
40 the head changed or no exact-head result exists. Reuse valid exact-head evidence. A red
41 gate stops the iteration; fix the gate before touching review comments.
422. Fetch every open thread. See "Fetching everything". Do not filter to one reviewer.
433. Triage by content, not by author. For each thread decide: actionable (a code change is
44 needed), informational (no change, reply explains why), or false positive (reply explains
45 why). Record the decision. A reply that proves inherited behavior or base parity without
46 a code push keeps prior exact-head evidence valid. Do not rerun gates or restart proof and
47 description work.
484. Fix every actionable item in one pass. Read the file at the cited line, understand the
49 comment in context, make the change.
505. If code changed, run the gate command again. Do not push a red gate.
516. If code changed, commit and push. Commit message names the iteration, for example
52 `Address review feedback (review-loop iteration 2)`.
537. Reply and resolve. See "Human threads" and "Bot threads". A reply goes on the thread that
54 raised the point, not in a new top-level comment.
558. Re-request review once per push. A reply without a push does not require another request.
56 See "Greptile specifics" and "Devin specifics". Wait for the results with a bounded poll.
579. Check the target. For `review-clean` or `merge-ready`, require zero unresolved threads
58 across every reviewer and, when Greptile is installed, a 5/5 score from its latest summary.
59 For `open-for-review`, require a non-draft PR and focused proof. Automated review may
60 remain pending.
61 
62If the cap is reached with threads still open, stop and report them; do not start another
63iteration.
64 
65## Fetching everything
66 
67Fetch all three comment sources on every iteration; a reviewer can use any of them.
68 
69```bash
70# Inline review threads with resolution state (GraphQL; paginate on endCursor)
71gh api graphql -f query='...' # see references/graphql-queries.md
72 
73# Reviews (approve, request changes, comment) with bodies
74gh api --paginate "repos/{owner}/{repo}/pulls/<PR_NUMBER>/reviews?per_page=100"
75 
76# Issue comments on the PR conversation tab (Greptile summaries, Devin notes, humans)
77gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100"
78```
79 
80A thread is open when `isResolved` is false. A review body or issue comment is a thread when it
81asks for a change or a reply; treat it as open until it is answered. Bot summaries that edit in
82place (Greptile does this) must be read by `updated_at`, not `created_at`.
83 
84## Human threads
85 
86- Reply on the thread with what was done and why, citing the commit. When nothing was
87 changed, say what was considered and why the code stays as is.
88- Never resolve a thread a human opened without replying first. Prefer to leave resolution of
89 human threads to the human unless the repository's policy says the author resolves.
90- Never dismiss a human review. A "changes requested" review is cleared by the reviewer
91 re-reviewing, and re-requesting review is the only action the loop takes on it.
92 
93## Bot threads
94 
95Resolve a bot thread after its item is fixed or answered. Use the GraphQL
96`resolveReviewThread` mutation, batched with aliases, and assert the result:
97 
98```bash
99gh api graphql -f query='
100mutation {
101 t1: resolveReviewThread(input: {threadId: "ID1"}) { thread { isResolved } }
102 t2: resolveReviewThread(input: {threadId: "ID2"}) { thread { isResolved } }
103}'
104```
105 
106Every alias in the response must report `isResolved: true`. If one does not, re-fetch the
107thread and treat it as still open; do not count it as resolved. Batch up to 20 aliases per
108request. Full queries are in `references/graphql-queries.md`.
109 
110Resolving is only the GraphQL `resolveReviewThread` mutation. A REST reply (the
111`pulls/{pr}/comments/{id}/replies` endpoint) answers a thread but never resolves it, and a
112replied-but-unresolved thread still fails the zero-unresolved exit criterion. Always resolve
113through GraphQL, never assume a reply resolved anything.
114 
115## Greptile specifics
116 
117- Before posting a trigger comment, check for a run already in progress. Look at
118 `gh pr checks <PR_NUMBER> --json name,state` and at
119 `repos/{owner}/{repo}/commits/<HEAD_SHA>/check-runs` for a check whose name matches
120 `greptile` (case-insensitive). If its state is `PENDING` or `IN_PROGRESS`, do not post; wait
121 for it.
122- The trigger comment text is configurable; the default is `@greptile-apps review`. Post it at
123 most once per push.
124- Poll the check run at 10-second intervals for up to 10 minutes. A check run that has only
125 just appeared but is still `PENDING` or `IN_PROGRESS` is not a result: wait for a terminal
126 `conclusion` AND for the summary comment's `updated_at` to move past the trigger time before
127 reading a score. A freshly-appeared check run beside an unchanged summary means Greptile has
128 started but not finished, and reading the old summary then records a stale score.
129- On large PRs the tagged review may never create a check run for the new head; Greptile instead
130 edits its existing summary comment. If no check run appears after a few attempts, poll the most
131 recently updated Greptile issue comment and stop when its `updated_at` is later than the trigger
132 comment and its body carries a score.
133- Read the score from three places and use the most recently updated one: the most recently
134 updated Greptile-authored issue comment (it edits in place, so sort by `updated_at`), the PR
135 body, and the latest review from `greptile-apps[bot]` or `greptile-apps-staging[bot]`. The
136 score looks like `3/5`, `5/5`, or `Confidence: 3/5`.
137- Freshness gate: a score counts only for the current head. Resolve the commit it was produced
138 for (the Greptile check run's `head_sha`, or a summary whose `updated_at` post-dates the head
139 commit) and compare it to the PR's current head SHA. A score from an earlier commit is stale:
140 re-trigger, wait, and never record it as the exit criterion.
141- Carry forward the items under "Prompt to fix all with AI" in the Greptile summary comment,
142 even when the inline comment endpoint returns zero unresolved comments. They count as open
143 until fixed or answered.
144- For `review-clean` or `merge-ready`, Greptile requires `5/5` and zero unresolved
145 Greptile threads.
146 
147## Devin specifics
148 
149- Devin reviews when the PR becomes ready for review. A draft PR never gets a Devin review.
150 If the PR is a draft and the caller wants Devin's review, mark it ready (`gh pr ready`) and
151 say so in the report.
152- Devin posts inline threads and a summary issue comment. Triage them like any other thread;
153 reply and resolve through the same GraphQL path.
154- Devin may report more issues than it posts ("N issues, M not posted by your settings"). The
155 flags hidden by GitHub settings are not fetchable through the API and are not review threads:
156 do not count them toward unresolved threads. Record them in the report as "M Devin flags not
157 visible via API (see the Devin dashboard)" for a human, but the exit criterion is over the
158 threads that actually exist on the PR.
159- Re-requesting Devin is a push; there is no trigger comment.
160 
161## Stop on timeout
162 
163If a poll for review results times out, stop the loop and report the timeout. Never continue
164with stale or missing review results, and never count a thread as resolved because the reviewer
165did not answer.
166 
167## Report
168 
169End with this table. Print it even when the loop stops early.
170 
171| Field | Value |
172|---|---|
173| Repository / PR | `owner/repo#N` |
174| Iterations | N of max M |
175| Threads found (per reviewer) | Greptile N, Devin N, humans N |
176| Resolved this run | N |
177| Replied, left open for a human | N |
178| Remaining | N, then one line each: `path:line` and a short quote |
179| Final Greptile score | X/5, or "not installed" |
180| Stop reason | exit criterion met, max iterations, timeout, red gate, auth failure |
181 
182Write the report and every reply through `unslop` before posting.
183 

Discussion