All skills
github avatar

/copilot-pr-autopilot

@d47a6c9 official
by githubgithub/awesome-copilot40k stars
5,040

Copilot left 14 review comments on your PR — half are nits. Hours of fix → reply → resolve → re-request, and each round lands MORE comments. This skill runs loop engineering: auto-triggers Copilot Code Review via GraphQL (no @copilot mention), triages every open thread (Copilot, humans, advanced-security) with a fix / decline / escalate rubric, dispatches parallel fix sub-agents that obey the repo build/test/lint conventions, commits per iteration, replies+resolves citing the pushed SHA, then re-triggers until HEAD is reviewed with zero threads awaiting the agent's reply (remaining open threads are explicit hand-offs to the human — escalated declines, design tradeoffs). You merge a clean PR; the bot runs it. Trigger phrases: "address copilot comments", "run a copilot review loop", "fix this PR", "iterate on copilot feedback". Repo-agnostic, gh CLI + PowerShell. Full autopilot needs repo Triage/Write; external PR authors get single-iteration mode plus manual re-trigger (UI 🔄 or substantive-commit push).

Use this Skill: https://skilld.dev/gh/github/awesome-copilot/copilot-pr-autopilot

This session only. Nothing lands on disk.

referencesapi-quirks.md

≈2.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

GitHub API Quirks (Verified)

API behaviors that matter for the Copilot review loop. All verified against the current API surface — read this before reaching for an alternative API or modifying the bundled scripts.

GraphQL trigger — requestReviewsByLogin is the supported path

mutation($p: ID!) {
  requestReviewsByLogin(input: {
    pullRequestId: $p,
    botLogins: ["copilot-pull-request-reviewer"]
  }) {
    pullRequest { number }
  }
}

Verified empirically against personal repos without Copilot Pro AND org repos with Copilot Enterprise. Works for both initial-add and re-request (no special re-request mutation).

Three GraphQL traps:

  1. Mutation is requestReviewsByLogin, NOT requestReviews. RequestReviewsInput (used by requestReviews) does not expose a botLogins field, so it can't request a bot reviewer at all — botLogins is the central field on requestReviewsByLogin.
  2. Field is botLogins, NOT userLogins. The latter returns Could not resolve user with login 'Copilot'.
  3. Slug is copilot-pull-request-reviewer (the App slug). The display login Copilot returns Could not resolve bot with slug 'Copilot'.

Verify success via a new copilot_work_started event on the issue's events feed — GET /repos/{o}/{r}/issues/{n}/events (see SKILL.md Gotchas "HTTP 200 / exit 0 is NOT proof"). Empirically this event type IS exposed on the /events endpoint (verified across 20+ trigger rounds on PR 236); it is not timeline-only. 01-request-review.ps1 enforces this by comparing the event id (monotonic) before and after the trigger.

Other trigger paths — DO NOT USE

  • requestReviews with botLogins → input type rejects the field. Don't try variants.
  • REST POST /pulls/<n>/requested_reviewers with reviewers[]=Copilot → can return HTTP 201 while silently dropping the bot. Not used by the script.
  • gh pr edit --add-reviewer Copilot → returns 'Copilot' not found on current gh. Not used by the script.

GraphQL latestReviews — stale cache, do NOT use

# DO NOT — stale projection:
pullRequest(number:$pr){ latestReviews(first:50){ nodes{...} } }

# USE INSTEAD — always current:
pullRequest(number:$pr){ reviews(last:100){ nodes{...} } }

latestReviews is a "latest per user" projection with stale-cache behavior: a fresh Copilot review can be absent for several minutes after submission, while reviews(last:100) reflects it immediately. Using latestReviews for in-flight or convergence checks causes the script to operate on an obsolete commit OID — either falsely declaring convergence or timing out for a review that already exists.

02-check-review-status.ps1 uses reviews(last:100) filtered client-side to the Copilot reviewer login. It also emits a stderr warning when the result is exactly 100 reviews, so the caller knows the boundary was hit and the latest Copilot review COULD be older than the window — practically only possible if 100+ non-Copilot reviews landed after the last Copilot review, which doesn't happen in normal use. If you ever see the warning and the loop misbehaves, fetch the full review list manually:

gh pr view <n> --json reviews --jq '.reviews[] | select(.author.login | test("copilot-pull-request-reviewer"))'

Tie-break for multiple Copilot reviews

When more than one Copilot review shares the same submittedAt (rare server-side clock collision under burst re-triggers), the script first prefers the review whose commit.oid == HEAD, then falls back to a stable sort. The intent is "the review that matches the current code is the one the agent should reply to" — preventing a stale-OID review from winning the tie and falsely flipping ReviewAtHead to false.

Reply + resolve mutations — both work

mutation($tid: ID!, $body: String!) {
  addPullRequestReviewThreadReply(input: {
    pullRequestReviewThreadId: $tid,
    body: $body
  }) { comment { id } }
}

mutation($tid: ID!) {
  resolveReviewThread(input: { threadId: $tid }) {
    thread { isResolved }
  }
}

isOutdated ≠ isResolved — current unresolved state is truth

A thread can be isOutdated: true (Copilot's comment points at lines that have since changed) while still isResolved: false. These threads:

  • Still need reply + resolve in the per-round loop. A thread can become outdated mid-round when your own fix shifts the cited lines. Filtering on !isOutdated would silently drop those threads, leaving the PR's open-conversations list non-empty even after the underlying code is fixed.
  • 03-list-open-threads.ps1 therefore lists every unresolved thread with no isOutdated filter.
  • 10-cleanup-outdated.ps1 is a safety net only — for the rare case where a thread becomes outdated AFTER your last per-round fetch.

Review latency — don't poll faster than ~3 min

Copilot reviews typically post 3–6 minutes after the request, occasionally up to ~10 minutes. There is no progress signal; polling more often than every ~3 min wastes API budget without making the review arrive sooner.

gh api graphql -F coerces strings — use -f for String!

The gh CLI distinguishes its two flag forms:

  • -F key=value — type inference. Values parsing as int, bool, or null are sent as that JSON literal.
  • -f key=value — always sends as raw string.

For any GraphQL variable declared String! (e.g. owner, repo, body, tid, after), use -f at call sites. A reply body that happens to be "true", "null", or all digits would otherwise be coerced and the call fails with a type error. Keep -F only for genuinely numeric or boolean variables (e.g. pr: Int!).

Note: the shared Invoke-Gh wrapper may internally rewrite -f field=<body> into -F field=@<tempfile> when the body contains embedded " (Windows PowerShell 5.1 native-arg quoting bug — see below). Even via @file, -F still applies type inference to the file content (gh's documented behaviour) — this rewrite is safe only because the rewrite trigger ("body contains "") guarantees the content is a string that no JSON literal (123, true, null, etc.) would match. Treat this -F ...=@file usage as an internal transport detail of the wrapper, not as permission to use -F ...=@file for arbitrary strings at call sites.

# Wrong — body could be coerced AND, under Windows PowerShell 5.1,
# any embedded `"` in $Body will be mis-split by the native-arg
# passer (gh sees a truncated body or a "received N args" error).
gh api graphql -f query=$q -F body=$Body

# Right — go through Invoke-Gh / Invoke-GhGraphQL. The shared helper
# auto-rewrites `-f field=<body>` and `-F field=<body>` pairs whose
# body contains `"` to `-F field=@<tempfile>` so the value is read
# from disk and never appears on the command line. This works
# identically on Windows PowerShell 5.1 and PowerShell 7+.
Invoke-GhGraphQL -GhArgs @('-f',"query=$q",'-f',"body=$Body") -Context 'reply body'

Calling gh directly (e.g. via & gh ... or raw gh api graphql) bypasses the cross-version tempfile rewrite — if your value contains " you'll re-introduce the PowerShell-5.1-only splitting bug. Always funnel gh calls through Invoke-Gh / Invoke-GhGraphQL.

Native gh exit codes bypass $ErrorActionPreference

gh is a native executable, not a PowerShell cmdlet, so a non-zero exit does not throw even when $ErrorActionPreference = 'Stop'. Without an explicit check the script will print misleading success messages after a failed API call, and the loop will falsely declare convergence on auth issues, rate limits, or transient 5xx.

Additional trap: gh api graphql can exit 0 for an HTTP 200 whose JSON body carries a top-level errors array. Treat that as a failed call too.

The shared helpers in scripts/_lib.ps1 (Invoke-Gh and Invoke-GhGraphQL) run gh via & gh @args with stderr redirected to a temp file (2>$errFile), then read $LASTEXITCODE and return {ExitCode, Stdout, Stderr}. Invoke-GhGraphQL additionally parses the GraphQL errors array on the response body and throws on either failure mode. All bundled scripts dot-source _lib.ps1 and use these wrappers — do the same in any new script.

git stash push argument order

git stash push -m "local-build" -- src/path/a src/path/b   # correct
git stash push -- src/path/a src/path/b -m "local-build"   # SILENTLY drops -m

The -m MUST come before the -- path separator.

Source: SKILL.md on GitHub

2 warnings3mo3 checks · Risk SAFE
  • Gen Agent Trust Hub3mo

    This skill provides a structured autopilot workflow for addressing GitHub pull request review comments. It uses the GitHub CLI and PowerShell to trigger reviews, triage feedback, apply code changes, and verify them via repository-specific build and test commands.

  • Socket3mo

    1 alert: gptAnomaly

  • Snyk3mo

    Risk: MEDIUM · 1 issue

Signed by skilld at d47a6c9. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 months ago

README badge

README badge for github/awesome-copilot/copilot-pr-autopilot