From 9646f392110e0afb8be369abefbcc39fcd1a0157 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 13 Jul 2026 20:09:13 -0700 Subject: [PATCH] copilot-instructions: repo-wide bot-node-id fallback for headless cold start (#295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #294. ## Problem The headless Copilot-review loop needs the Copilot reviewer's bot node id to drive round 1 via the `requestReviews` mutation. On a **cold-start PR** - freshly opened, auto-review-on-open not yet landed, so **no formal review and no issue comment on the PR yet** - the runbook's only documented source for that id was **manual UI seeding**, which defeats a headless/cron run. Surfaced running the loop fully headless on `ptr727/HomeAssistant-Config` PR #7. ## Fix The Copilot reviewer's bot node id is the reviewer bot *account's* node id and is **stable repo-wide**. Verified empirically - identical across every recent PR in two repos: ``` HomeAssistant-Config -> BOT_kgDOCnlnWA ProjectTemplate -> BOT_kgDOCnlnWA ``` So document reading the id from **any prior Copilot review anywhere in the repo** as the preferred cold-start fallback (a `pullRequests(last:20)` GraphQL query), positioned **before** UI seeding. UI seeding drops to a true last resort: only a repo that has never had a Copilot review at all, where no prior id exists to read. Doc-only change to `.github/copilot-instructions.md`; the `requestReviews` mutation itself is unchanged. markdownlint + cspell clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .github/copilot-instructions.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3397e051..ec55c76b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -56,7 +56,28 @@ mutation($pr: ID!, $bot: ID!) { }' -F pr="$PR_NODE" -F bot="$BOT_ID" ``` -The bot node id is read from an existing Copilot **formal** review (`pullRequest.reviews`), so step 1 needs at least one prior formal review on the PR - the auto-review-on-open normally supplies the first one (it may have **no inline comments**; that still counts, and its bot node id is still readable). Poll for it (give auto-review-on-open a few minutes) before deciding it is missing. If Copilot posted **only an issue comment** and no formal review, the head is covered but `reviews` yields no bot node id - read the id from the Copilot issue comment's author by querying the PR's issue comments in GraphQL (`pullRequest.comments` -> author `... on Bot { id }`), or request `Copilot` once through the GitHub PR UI to produce a formal review. Manual UI seeding is the fallback specifically when no formal review exists to read the id from; then use the mutation for every subsequent re-request. +The bot node id is read from an existing Copilot **formal** review (`pullRequest.reviews`), so step 1 needs at least one prior formal review on the PR - the auto-review-on-open normally supplies the first one (it may have **no inline comments**; that still counts, and its bot node id is still readable). Poll for it (give auto-review-on-open a few minutes) before deciding it is missing. + +**Cold start (round 1 not yet landed): read the id repo-wide, not from this PR.** The Copilot reviewer's bot node id is the reviewer bot *account's* node id and is **stable across every PR in the repo**. So a freshly opened PR that has neither a formal review nor an issue comment yet does **not** need UI seeding to bootstrap the id - read it from any prior Copilot review anywhere in the repo, then feed it into the `requestReviews` mutation to drive round 1. Query the **most recent** PRs (`first: 20` with an explicit newest-first order; plain `last: 20` returns the *oldest* PRs, which may predate Copilot on the repo), and **guard for an empty result** - an empty `$BOT_ID` means none of the sampled PRs carry a Copilot review. Widen the window (raise the count or paginate) before concluding the repo has never had one and falling back to UI seeding; never feed an empty id into the mutation: + +```sh +BOT_ID=$(gh api graphql -f query=' +{ + repository(owner: "", name: "") { + pullRequests(first: 20, orderBy: { field: CREATED_AT, direction: DESC }) { + nodes { reviews(first: 20) { nodes { author { __typename login ... on Bot { id } } } } } + } + } +}' --jq '[.data.repository.pullRequests.nodes[].reviews.nodes[] + | select(.author.login == "copilot-pull-request-reviewer") + | .author.id] | first // empty') +if [ -z "$BOT_ID" ]; then + echo "no Copilot review in the 20 most recent PRs - widen the window, else fall back to UI seeding" >&2 + return 1 2>/dev/null || exit 1 # stop; do NOT call requestReviews with an empty id +fi +``` + +If Copilot posted **only an issue comment** on this PR and no formal review, you can instead read the id from that comment's author (`pullRequest.comments` -> author `... on Bot { id }`). Manual UI seeding is the last resort - needed only for a repo that has **never** had a Copilot review, so no prior id exists anywhere to read; then use the mutation for every subsequent re-request. **Do NOT post `@Copilot review` as a PR comment.** That comment triggers the Copilot *coding agent* (`copilot-swe-agent[bot]`), which makes code changes rather than posting a review.