From copilot-review
Runs a single Copilot review check-fix cycle on a PR using gh CLI. Use this skill whenever the user wants to: check and fix Copilot review comments, request a Copilot code review on a PR, or iterate on PR feedback. Trigger on phrases like 'copilot review', 'fix PR comments', 'iterate on PR', 're-review', or when the user mentions 'gh pr' with 'copilot'. Combine with /loop for automated repeated cycles, e.g. '/loop 2m /copilot-review'.
How this skill is triggered — by the user, by Claude, or both
Slash command
/copilot-review:copilot-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A single-pass check-fix cycle for Copilot review comments. Designed to be called repeatedly via `/loop`:
A single-pass check-fix cycle for Copilot review comments. Designed to be called repeatedly via /loop:
/loop 2m /copilot-review
Each invocation runs one iteration: check for unresolved comments → fix → push → resolve → re-trigger review. The loop terminates ONLY when Copilot's review body contains "generated no new comments" or "generated 0 comment".
gh CLI v2.88.0+gh auth statusIf the user provides a PR number and repo, use those directly. Otherwise, auto-detect from the current branch:
gh pr view --json number,url,headRepository -q '
"PR #\(.number) — \(.headRepository.owner.login)/\(.headRepository.name)\n\(.url)"
'
If no PR is found on the current branch, ask the user for the PR number and repo.
Extract and store:
OWNER — repository ownerREPO — repository namePR_NUM — pull request numberBefore acting on comments, confirm two things: (a) Copilot has finished reviewing, and (b) the review covers the latest push. Without the timing check, a common race condition occurs: after fixing and re-triggering, the next cycle sees the old review (state = COMMENTED), finds all old threads resolved, and falsely concludes "no unresolved comments" — stopping the loop before Copilot's new review arrives.
IMPORTANT: Use GraphQL (not gh pr view --json reviews) to fetch reviews. The REST API returns author.login as "copilot", but GraphQL returns "copilot-pull-request-reviewer". Using GraphQL consistently avoids mismatches.
Use a single GraphQL query to get the latest Copilot review (with body), the latest commit timestamp, and all unresolved threads:
gh api graphql -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviews(last: 20) {
nodes {
author { login }
state
submittedAt
body
}
}
commits(last: 1) {
nodes { commit { committedDate } }
}
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 10) {
nodes {
body
author { login }
path
line
originalLine
}
}
}
}
}
}
}
' -F owner="${OWNER}" -F repo="${REPO}" -F pr="${PR_NUM}" \
--jq '{
review: ([.data.repository.pullRequest.reviews.nodes[]
| select(.author.login == "copilot-pull-request-reviewer")]
| sort_by(.submittedAt) | .[-1]
| {state, submittedAt, body}),
latestCommit: .data.repository.pullRequest.commits.nodes[-1].commit.committedDate,
unresolvedThreads: [.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved == false)
| select(.comments.nodes[0].author.login == "copilot-pull-request-reviewer")
| {
threadId: .id,
path: .comments.nodes[0].path,
line: (.comments.nodes[0].line // .comments.nodes[0].originalLine),
comment: .comments.nodes[0].body
}]
}'
Apply the following decision logic in order:
gh pr edit ... --add-reviewer "@copilot" → return (end this cycle, wait for next /loop invocation).review.submittedAt < latestCommit — Copilot has not reviewed the latest push. Report "Waiting for Copilot to review latest push..." and return (end this cycle; do NOT stop the loop).review.submittedAt <= lastSeenReviewAt — If you recorded a lastSeenReviewAt value from a previous cycle (Step 8), and the current review.submittedAt is equal to or older than that value, the re-triggered review has not arrived yet. Report "Waiting for re-triggered Copilot review to arrive..." and return (end this cycle; do NOT stop the loop). Do NOT reason about whether Copilot "will" or "won't" re-review. Copilot was already re-triggered in the previous cycle's Step 8 — it WILL produce a new review; it simply hasn't arrived yet. This is true even when all threads are resolved and no code was pushed. NEVER terminate the loop from this check. If review.submittedAt is newer than lastSeenReviewAt, the new review has arrived — clear lastSeenReviewAt and continue to check #4.review.body — Copilot's review summary always contains a line like:
"generated no new comments" or "generated 0 comment" → Copilot reviewed and found nothing. Report "Copilot review passed (no new comments) — ready for human review" and STOP THE LOOP (terminate entirely). This is the ONLY check that may terminate the loop."generated N comments" (N > 0) → Copilot found issues. Proceed to Step 3.The unresolvedThreads array from the Step 2 query already contains all unresolved threads authored by Copilot.
If review.body contains "generated no new comments" or matches "generated 0 comment", Copilot found no issues. Report "All Copilot comments resolved — ready for human review" and stop the loop.
If review.body contains "generated N comments" (N > 0) but unresolvedThreads is empty, this means all threads were already resolved — but Copilot may find new issues on re-review. Do NOT stop. Proceed to Step 7 (resolve any remaining threads) and Step 8 (re-trigger) to let Copilot confirm with a fresh review.
If unresolvedThreads is not empty, proceed to fix each comment.
For each unresolved comment:
Do not blindly accept every suggestion. Copilot may repeat already-resolved comments or suggest changes that conflict with project architecture. If a suggestion is incorrect or irrelevant, skip it and note the reason.
If no code changes were made (all suggestions skipped as incorrect or irrelevant), skip Steps 5 and 6 — no tests to run and no commit/push needed. Proceed directly to Step 7 to resolve the evaluated threads, then Step 8 to re-trigger. Note: since no new commit is pushed in this case, lastSeenReviewAt tracking (Step 2 check #3) is essential so the next cycle's Check #3 will correctly return (end that cycle) while waiting for the re-triggered review, rather than proceeding with stale data. The re-triggered review WILL arrive eventually — the next cycle just needs to wait.
Run the project's test suite before committing. A fix that breaks other things is worse than the original issue.
If tests fail:
Use the commit-message skill (/commit-message) to generate a proper conventional commit message for the fixes.
After committing, push the changes:
git push
After pushing, resolve each thread that was successfully addressed by inlining its ID:
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "<THREAD_NODE_ID>"}) {
thread { isResolved }
}
}
'
gh pr edit ${PR_NUM} --repo ${OWNER}/${REPO} --add-reviewer "@copilot"
After re-triggering, record lastSeenReviewAt = the current review.submittedAt from this cycle's Step 2 query. The next /loop cycle will use this value in Step 2 check #3 to detect whether a new review has arrived before proceeding.
/loop 2m /copilot-review
Or with a specific PR:
/loop 2m Check Copilot review on PR #123 (owner/repo) using /copilot-review
gh pr edit <PR_NUM> --repo <OWNER/REPO> --add-reviewer "@copilot"
For quick inspection without thread IDs — useful for a summary view:
gh api "repos/${OWNER}/${REPO}/pulls/${PR_NUM}/comments" \
--jq '.[] | select(.user.login | test("copilot"; "i"))
| "### \(.path):\(.line // .original_line)\n\(.body)\n"'
author.login as "Copilot", but GraphQL returns "copilot-pull-request-reviewer". Always use GraphQL with exact match == "copilot-pull-request-reviewer" for reliable detection."generated no new comments" means Copilot is done with no issues. "generated N comments" means there are comments to address. Do NOT rely solely on unresolved thread count — threads from previous reviews may all be resolved while a new review hasn't arrived yet.latestCommit doesn't change, so the timing check (review.submittedAt < latestCommit) alone cannot detect a stale review. The lastSeenReviewAt comparison (Step 2 check #3) is the reliable mechanism for this case./loop call the skill again later." They NEVER terminate the loop. Only Check #4 ("generated no new comments" / "generated 0 comment") may terminate the loop entirely. When Check #3 fires (re-triggered review hasn't arrived), do NOT combine it with observations like "all threads are resolved" or "no code was pushed" to conclude the loop should stop. Copilot will produce a new review after being re-triggered — it just takes time.unresolvedThreads is empty or because all threads are resolved. If review.body says "generated N comments" (N > 0), threads may have been resolved but Copilot hasn't re-reviewed yet. Only terminate the loop when Copilot explicitly reports "generated no new comments" or "generated 0 comment" in review.body. No other condition — not empty threads, not "no code pushed", not any combination — justifies terminating the loop..github/copilot-instructions.md to guide Copilot's review behavior (coding style, conventions, etc.).Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub appleboy/skills --plugin copilot-review