Receiving PR Reviews
SkillProductivityWork through every unresolved review thread on a PR to completion — validate, fix if warranted, reply, resolve, then re-check on a bounded schedule. Use after pushing a commit to a PR, or when asked to check or address PR reviews.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Receiving PR Reviews skill
What this skill tells your AI
The instructions your AI receives, as published by jamie-bitflight/claude_skills in .agents/skills/receiving-pr-reviews/SKILL.md and read by ahel’s review.
-
Fetch every unresolved thread, every unresponded review, and Codex's approval state. Prefer
--summary— it already carries every id step 4/5 needs; drop it only when you needreviews_with_body's full list or a thread's complete comment history:uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py fetch --pr <N> --summaryIf the helper cannot use
ghand GitHub MCP tools are available, use the lightweight GitHub MCP fallback for this workflow instead. Do not install or reconfigureghmerely to avoid the fallback, and do not run both paths for the same snapshot.Read
reviews_count,threads_count,unresolved_count,unresponded_count, andblockerstogether — never treat an emptyunresolvedarray on its own as "nothing to do". Athreads_countof 0 means no inline thread landed, not that no review landed. A non-emptyblockersmeans the empty result set is expected and the fix is on the PR itself — undraft it, resolve the conflicts — not in the review queue. (Dropping--summarygets the same fields underreviewability.blockersinstead of top-levelblockers, plus the fullreviews_with_bodyand each thread's completecommentslist — a thread'scomments_truncated: truethere means it has passed 100 comments; page itscommentsconnection directly before concluding anything about it.unresponded_countitself only exists on--summaryoutput — the full form has no matching field, uselen(unresponded_reviews)there instead.)unresolved/unresponded_reviewsentries are this run's actionable input; treat every one as something to address. Forcodex_approved, see step 7. Checking several PRs at once:--pr 41,42,44prints one line (or, with--summary, one JSON block) per PR instead of one call each. -
For each unresolved thread or unresponded review: read it, validate the claim locally, assess against the change goal and repository instructions.
-
Implement, commit, and push a fix only when it improves the product — push before replying, so the SHA named in the reply is inspectable and resolving the thread never outruns what is actually on the remote.
-
Reply on that thread with the disposition — conclusion, evidence, commit SHA, or why no change was warranted:
uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py reply --pr <N> --comment-id <databaseId> --body '...' -
Resolve the thread:
uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py resolve --thread-id <id>Steps 4 and 5 combined — one thread or many in one process:
uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py reply-and-resolve \ --pr <N> --thread-id <id> --comment-id <databaseId> --body '...' uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py reply-and-resolve-batch \ --pr <N> --input-file threads.json # [{thread_id, comment_id, body}, ...]The batch form stops at the first failure and prints one JSON line per thread.
-
A decision spanning threads (PR sequencing, rebase disposition), or a response to a
reviews_with_body/unresponded_reviewsentry, goes on the PR itself viagh pr comment <N> -R <owner>/<repo>— the same owner/repo this run used in step 1 — before the work it governs. When answering a specific entry, quote that review's ownurlfield from step 1's output in the comment body. That quotedurl, postdating the review, is what clears the review out ofunresponded_reviewson the next check; chronological order alone does not. -
Once all current threads and reviews are addressed, re-check with
watch, looping short calls rather than one long block:uv run ./.agents/skills/receiving-pr-reviews/scripts/pr_review_threads.py watch --pr <N>Block on it inline when there is no other work to advance. With other work queued, background the call using whatever mechanism the harness provides and continue that work — then poll the backgrounded call for its own result before reporting back or finishing, because it produces no completion notification.
timed_out: falsemeansstate.unresolved_count > 0,state.unresponded_reviewsis non-empty, orstate.codex_approvedistrue— restart this skill from step 1 against whichever is true.timed_out: truemeans none of the three were true inside that one call's window, not that watching is done — issue anotherwatchimmediately to keep covering the window you intend to watch. Stop once one of the three conditions is met, or once the intended window is covered.codex_approved: trueon its own, withunresolved_count: 0andunresponded_reviews: [], is a completion signal — do not re-enter step 1 for it.
fetch/watch/replydetect this checkout's own repository viagh repo view; pass--github owner/repoto target a different one, or when detection fails.--gh-timeout-secondsis unbounded by default onfetchandwatch. One snapshot is seven sequentialgh apicalls, some of them paginating a large PR, so choose a bound against your own network. Insidewatchit applies to the first fetch only; each poll is bounded by the time left before--timeout-seconds.reply's--comment-idis a comment'sdatabaseIdandresolve's--thread-idis a thread'sid— both come straight from step 1's output. When a thread already has more than one comment, pass the first comment'sdatabaseId:commentsis in creation order, and GitHub rejects a reply targeted at another reply.- A
reviews_with_body/unresponded_reviewsentry is not a thread, so it cannot be replied to or resolved through this script. Address it and post the response on the PR itself per step 6. watch's defaults — a 90-second poll interval, a 270-second timeout per call — stay under the 5-minute prompt-cache TTL floor that applies in every Claude billing mode. Cover a longer window by looping calls, not by raising--timeout-seconds.watchstops polling once less than one interval remains before its deadline, so its last observed state can be up to one interval stale. The next call's own first fetch covers that stretch.- Every check inside
watchis a freshghsnapshot with no baseline, so a call whose first fetch already has outstanding work returns immediately. Callingwatchright after aresolveor a plainfetchis safe. reviewabilityis read fresh on every poll, so atimed_out: trueresult carries it too — checkstate.reviewability.blockersbefore issuing anotherwatchrather than waiting out a window for reviews that cannot arrive.mergeable: "UNKNOWN"is never a blocker: GitHub computes mergeability in a background job, and it resolves on a later check.watchexits non-zero with nothing on stdout when the last re-poll of a window failed. Retry the call rather than reading it as "nothing new."--summary(step 1) also works onwatch;timed_outsits alongside the summary fields there instead of nested understate. Add--max-body Nto cut long bodies, visibly marked when cut; unlimited by default. Step 7'sstate.Xfield names (state.unresolved_count,state.unresponded_reviews,state.codex_approved,state.reviewability.blockers) describe the full (non---summary) form only — with--summary, read the same signals without thestate.prefix, and readblockersat the top level rather than underreviewability.- Comma-separated
--prisfetch-only —watchpolls one PR at a time.
Signals
- GitHub stars
- 66
- Forks
- 10
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
receiving-pr-reviews- Source
- github.com/jamie-bitflight/claude_skills