gh-pilot
GH Pilot
Run a lightweight Copilot review loop on one PR until feedback is fully addressed.
Inputs
- PR number (optional): If not provided, detect the PR for the current branch.
Instructions
1. Identify the PR
gh pr view --json number,headRefName -q '{number: .number, branch: .headRefName}'
2. Loop
Repeat this cycle. Max 5 iterations to avoid runaway loops.
A. Fetch Copilot state first
gh api --paginate repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/reviews
Fetch review threads with resolution state (paginate if needed):
THREADS_FILE="$(mktemp)"
CURSOR=""
while true; do
PAGE=$(gh api graphql \
-F owner="<OWNER>" \
-F repo="<REPO>" \
-F pr=<PR_NUMBER> \
${CURSOR:+-F cursor="$CURSOR"} \
-f query='
query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
isResolved
isOutdated
comments(first: 10) {
nodes { id databaseId body author { login } createdAt }
}
}
}
}
}
}')
echo "$PAGE" | jq -c '.data.repository.pullRequest.reviewThreads.nodes[]' >> "$THREADS_FILE"
HAS_NEXT=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
[ "$HAS_NEXT" = "true" ] || break
CURSOR=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
done
From these results, compute:
last_copilot_review_idfrom the latest review by:copilot-pull-request-reviewer[bot]copilot-pull-request-reviewerCopilot
last_copilot_review_commit_shafrom that same latest Copilot review (commit_id)current_pr_head_shafrom:gh pr view <PR_NUMBER> --json headRefOid -q .headRefOid
unresolved_copilot_thread_idsfrom threads where:isResolved == falseisOutdated == false- at least one comment author login is one of the Copilot logins above
outdated_copilot_thread_idsfrom threads where:isResolved == falseisOutdated == true- at least one comment author login is one of the Copilot logins above
comments(first: 10) is intentional to keep payload size small.
If Copilot participation is ambiguous for a thread, refetch that thread's
comments with a larger window before classifying it.
B. Bootstrap decision
- If Step H pushed code in the previous iteration, this takes priority: go to Step C regardless of other conditions.
- Otherwise, if
unresolved_copilot_thread_idsoroutdated_copilot_thread_idsis not empty, go to Step F to process them (skip reviewer request).- Process
unresolved_copilot_thread_idsfirst. - Then handle
outdated_copilot_thread_ids(resolve with rationale if superseded, or treat as actionable if still relevant).
- Process
- If there is no Copilot review yet, request Copilot review.
- Stop successfully only when:
unresolved_copilot_thread_idsis empty, andoutdated_copilot_thread_idsis empty, andcurrent_pr_head_sha == last_copilot_review_commit_sha.
C. Request Copilot review only when B says none exist
gh pr edit <PR_NUMBER> --add-reviewer "copilot-pull-request-reviewer"
If Copilot is already assigned and a fresh pass is needed:
gh pr edit <PR_NUMBER> --remove-reviewer "copilot-pull-request-reviewer"
gh pr edit <PR_NUMBER> --add-reviewer "copilot-pull-request-reviewer"
D. Wait for a new Copilot review (only when C ran)
Poll the reviews endpoint every 30 seconds until a new Copilot review appears
(review id differs from last_copilot_review_id captured in Step A).
Example:
LAST_ID="<LAST_COPILOT_REVIEW_ID_OR_0>"
for _ in {1..60}; do
LAST_LINE=$(gh api --paginate repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/reviews \
--jq '.[] | select(.user.login == "copilot-pull-request-reviewer[bot]" or .user.login == "copilot-pull-request-reviewer" or .user.login == "Copilot") | [.id, .commit_id] | @tsv' \
| tail -n 1)
NEW_ID=$(echo "$LAST_LINE" | cut -f1)
NEW_SHA=$(echo "$LAST_LINE" | cut -f2)
if [ -n "$NEW_ID" ] && [ "$NEW_ID" != "$LAST_ID" ]; then
break
fi
sleep 30
done
If no new Copilot review appears within 30 minutes, stop and report timeout. After new review appears, re-run Step A to refresh:
last_copilot_review_idlast_copilot_review_commit_shacurrent_pr_head_sha
Do not re-request Copilot again while waiting in this step.
E. Check exit conditions
Stop the loop if any condition is true:
- Latest Copilot review round is complete and:
unresolved_copilot_thread_idsis emptyoutdated_copilot_thread_idsis emptycurrent_pr_head_sha == last_copilot_review_commit_sha
- Max iterations reached (report remaining items).
Never treat "resolved by the agent" alone as terminal after a push; require a fresh Copilot review pass.
F. Process each Copilot thread
For each Copilot thread:
- Process
unresolved_copilot_thread_idsfirst, thenoutdated_copilot_thread_ids. - Read code context.
- Classify as
ActionableorNon-actionable. - If actionable, implement the fix and update tests/docs if needed.
- If non-actionable, prepare a short rationale reply.
- For outdated threads, explicitly decide and record:
superseded(reply with rationale and resolve), orstill relevant(treat as actionable).
G. Resolve and reply on threads
Fetch unresolved threads (all pages):
THREADS_FILE="$(mktemp)"
CURSOR=""
while true; do
PAGE=$(gh api graphql \
-F owner="<OWNER>" \
-F repo="<REPO>" \
-F pr=<PR_NUMBER> \
${CURSOR:+-F cursor="$CURSOR"} \
-f query='
query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
isResolved
isOutdated
comments(first: 10) {
nodes { id databaseId body author { login } }
}
}
}
}
}
}')
echo "$PAGE" | jq -c '.data.repository.pullRequest.reviewThreads.nodes[]' >> "$THREADS_FILE"
HAS_NEXT=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
[ "$HAS_NEXT" = "true" ] || break
CURSOR=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
done
comments(first: 10) is intentional here as well.
If you cannot find the needed Copilot comment databaseId for reply,
refetch that specific thread with a larger comments window before replying.
Resolve addressed threads:
for THREAD_ID in <THREAD_ID_1> <THREAD_ID_2> <THREAD_ID_N>; do
gh api graphql \
-F threadId="$THREAD_ID" \
-f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread { isResolved }
}
}'
done
If batching in one mutation, generate one alias per thread id (t1..tN) dynamically.
Reply to non-actionable comments with rationale:
gh api repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/comments -f body='Rationale here' -F in_reply_to=<COMMENT_DATABASE_ID>
Use the comment databaseId from the GraphQL thread query for in_reply_to.
H. Commit and push once for the iteration
git add -- <CHANGED_FILE_1> <CHANGED_FILE_2> <CHANGED_FILE_N>
git status --short
git commit -m "agent: address copilot review feedback (gh-pilot iteration N)"
git push
Verify staged files before committing; do not include unrelated files.
After pushing code changes, go to Step C to request a fresh Copilot pass. If no code changes were made in this iteration, skip commit/push and return to Step A.
3. Report
At the end, summarize:
| Field | Value |
|---|---|
| Iterations | N |
| Copilot comments resolved | N |
| Copilot comments remaining | N |
| Final state | Success or max-iteration stop |
Output format
gh-pilot complete.
Iterations: N
Resolved: X comments
Remaining: Y
Final state: success|max-iteration-stop
More from henryqw/skills
gh-autopilot
Run a standalone autonomous GitHub Copilot pull request review loop with explicit stage entry and event logs. Use when Codex should start from a user-selected stage (create PR, monitor review, or address existing comments), execute deterministic cycle transitions, and continue looping until Copilot reports no comments or the configured Stage 2 max-wait limit is reached.
10gh-pr-creation
Create a new GitHub pull request end-to-end when the user asks to open or create a PR. Use when Codex must turn local uncommitted work into a reviewable PR by making multiple scoped commits, running and passing all repository quality gates, renaming the branch so it reflects the changes, creating a Conventional Commits PR title, writing a PR description with summary/rationale/migration steps, and assigning Copilot as reviewer.
6gh-address-copilot-review
Handle GitHub PR review comments when comments are provided by the user as context. Use when Codex must evaluate comments one by one, classify each as actionable or non-actionable or needs clarification, implement only necessary fixes, keep changes scoped per comment, run validation, avoid intermediate pushes, perform one final push for the full batch, resolve addressed threads, respond to rejected comments with rationale, and re-request Copilot reviewer exactly once at the end via gh-assign-copilot-reviewer.
5triangulate
Evaluate supplied artifacts and return a consolidated findings table with evidence-based conclusions. Use this skill when the user wants a proposal, plan, code change, document, prompt, transcript, or other material reviewed through a structured multi-perspective evaluation instead of a single opinion.
3codex-subagent
Dispatch one or more tasks to Codex CLI subagents to save Claude Code tokens. Accepts explicit task descriptions, auto-selects sandbox (read-only vs workspace-write) and reasoning effort (high vs xhigh) based on task type, and collects structured results with durable artifacts.
2trueflow
Run the full generic trueflow pipeline by invoking `trueflow_initializer`, `trueflow_adversary`, and `trueflow_referee` in sequence, persisting stage outputs under `.context/trueflow/`, and returning a consolidated `findings.md` table. Use this skill whenever the user asks to "use trueflow" or wants multiple agents to review artifacts, solution proposals, coding implementation plans, documents, prompts, or other material and return adjudicated findings rather than a single opinion.
1