Skip to content

Make pr_review.py wait Request a Review, Not Just Poll for One - #685

Merged
ptr727 merged 4 commits into
developfrom
feature/pr-review-wait-auto-request
Aug 13, 2026
Merged

Make pr_review.py wait Request a Review, Not Just Poll for One#685
ptr727 merged 4 commits into
developfrom
feature/pr-review-wait-auto-request

Conversation

@ptr727

@ptr727 ptr727 commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Why

Twice in one session, wait polled the full 45-minute timeout for a review nothing had asked for: once after a push superseded the prior request, once after a re-request attempt that never actually issued a mutation. wait never requested anything itself, contrary to what a caller reasonably expects from a command named wait — the manual requestReviews recipe documented in .github/copilot-instructions.md is a separate step every caller has to remember on every push, and forgetting it fails silently (a full timeout, not an error) rather than loudly.

What changed

wait now issues the request itself, once, before its first poll, whenever nothing is already outstanding and the head isn't already covered. The bot id is read fresh from the repository's own last 20 PRs every time (never fabricated, never cached), and requests are additive (union:true) so a human reviewer requested alongside Copilot is never dropped. A repository with no Copilot review anywhere to read the id from falls back to plain polling rather than crashing or guessing.

This is a deliberate second write path, not an oversight fixed in passing: it was previously excluded by name in a guard test (test_the_only_writes_are_the_two_the_reply_path_owns) asserting an explicit prior design decision, itself consistent with .github/copilot-instructions.md's own text describing requestReviews as a manual mutation this script doesn't issue. Renamed that test and updated its assertions for the third deliberate write (now test_the_only_writes_are_the_three_named_here), added the mutation and runbook-mention checks alongside the existing two, and added an explicit ban on union:false (the destructive clear-and-recover form stays a manual runbook step, never automatic).

Added targeted tests for all three auto-request shapes: bot id resolved and the request fires, no bot id found anywhere and it falls back to polling, already requested and it is not re-requested. Each was verified to fail against the pre-fix code before trusting it (reverted pr_review.py alone, ran the three new tests, confirmed 3 of 4 failed as expected — the fourth, "already requested," passed even pre-fix since that no-op case existed by coincidence of no request-issuing code existing yet, not something this PR broke).

Verification

cd scripts && python3 -m unittest test_pr_review   # 231 tests, all pass
python3 scripts/prose_lint.py scripts/pr_review.py scripts/test_pr_review.py   # clean
uvx ruff check scripts/pr_review.py scripts/test_pr_review.py   # 23 pre-existing errors, unchanged by this diff, none in new code

🤖 Generated with Claude Code

Twice in one session, wait polled the full 45-minute timeout for a
review nothing had asked for: once after a push superseded the
prior request, once after a re-request attempt that never actually
issued a mutation. wait itself never requested anything, contrary
to what a caller reasonably expects from a command named wait,
since the manual requestReviews recipe documented in
.github/copilot-instructions.md is a separate step every caller has
to remember on every push.

wait now issues that request itself, once, before its first poll,
whenever nothing is already outstanding and the head is not
already covered. The bot id is read fresh from the repository's
own last 20 PRs every time (never fabricated, never cached across
runs), and requests are additive (union:true) so a human reviewer
requested alongside Copilot is never dropped. A repository with no
Copilot review anywhere to read the id from falls back to plain
polling rather than crashing or guessing.

This is a second write path this script now owns, deliberately: it
was previously excluded by name in
test_the_only_writes_are_the_two_the_reply_path_owns testing an
explicit prior design decision (confirmed against
.github/copilot-instructions.md's own text, which already described
requestReviews as a manual, undocumented-by-this-script mutation).
Renamed the test and updated its assertions for the third
deliberate write, added its mutation and runbook-mention checks
alongside the existing two, and added a same-string ban on
union:false, since the destructive clear-and-recover form stays a
manual runbook step, never something this script issues on its own.

Added targeted tests for all three auto-request shapes (bot id
resolved and the request fires, no bot id found anywhere and it
falls back to polling, already requested and it is not
re-requested), each verified to fail against the pre-fix code
before trusting them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates scripts/pr_review.py wait to proactively request a GitHub Copilot review (via requestReviews) when no review is outstanding and the head SHA is not yet covered, reducing cases where wait times out simply because no request was ever issued.

Changes:

  • Extend the live GraphQL query to include the PR node id and add an auto-request step before the first polling loop iteration.
  • Add GraphQL documents to discover the Copilot reviewer bot id from recent PR reviews and to issue the requestReviews mutation using union:true.
  • Update and add unit tests to cover the new auto-request behavior and to guard the script’s mutation surface.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
scripts/pr_review.py Implements auto-request logic for wait, adds bot-id discovery query and the requestReviews mutation.
scripts/test_pr_review.py Updates payload shapes and adds/adjusts tests to validate the new auto-request flow and mutation guardrails.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/pr_review.py Outdated
Comment thread scripts/pr_review.py
request_copilot_review()'s docstring claimed it never raises, but
it calls gh_graphql() directly (via copilot_bot_id() and the
mutation), which does raise on a genuine API failure. Corrected
the docstring: it raises the same as every other write in this
script, since silently swallowing that into a polling fallback
would recreate the exact blind-wait failure this function exists
to prevent, just from a different cause. The one quiet path stays
finding no bot id at all, which is not a failure.

Q_BOT_ID's comment claimed the bot id comes from the 'most recent
PRs' reviews', but reviews(first:20) fetches the oldest reviews in
each PR's own connection, not the newest. Functionally harmless
(the id is invariant per account, so which review it is read from
never matters once one is found), but the claim was wrong. Reworded
to describe what each part of the query actually does instead.

Both caught by Copilot's own review of this PR, requested by the
very wait fix this PR ships (the auto-request fired for real and a
review landed in under 3 minutes), the first live dogfood test of
the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 13, 2026 22:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

scripts/pr_review.py:40

  • The module docstring still describes reply as "The only writing path here", but wait now issues a requestReviews mutation as well. Update the docstring so it no longer claims reply is the only state-changing subcommand.
  wait     Request a review where none is outstanding, then poll until Copilot's review lands
           on the current head, then print the digest. The auto-request is skipped once a
           review already covers the head, once Copilot has already answered outside a formal
           review, or once one is already in the pending request set, so calling `wait` again on
           the same PR never double-requests. It reads the Copilot reviewer's bot id from the

The --help text for reply still said 'The only writing path here',
stale since wait now issues requestReviews too. Dropped the claim
rather than updating it to name both, since the subcommand's own
help text is not the place to track that count, this file's tests
are.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 13, 2026 22:38
@ptr727

ptr727 commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Answering a suppressed (low-confidence) finding not in any thread, scripts/pr_review.py:40: confirmed real. Fixed in bbe8c87: dropped the 'The only writing path here' claim from reply's --help text rather than updating it to name both writes, since the subcommand help text isn't the place to track that count, this file's own tests are (see test_the_only_writes_are_the_three_named_here).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

scripts/test_pr_review.py:2425

  • The union:false guard can be bypassed if the GraphQL document is formatted with newlines/tabs around the colon (e.g., union:\nfalse). Stripping only spaces makes this test less reliable than intended; use a whitespace-tolerant regex or strip all whitespace before matching so the guard actually enforces the rule.
        self.assertNotIn('union:false', source.replace(' ', ''),
                         'the additive form is the only one this script issues, since dropping '
                         'a pending human reviewer is the runbook\'s manual recovery path, never '
                         'an automatic one')

source.replace(' ', '') left a newline- or tab-separated
'union:\nfalse' able to slip past the guard undetected, which
would silently defeat the very check meant to catch a destructive
mutation form entering this script. ''.join(source.split()) strips
every whitespace variant, matching what the guard actually intends
to enforce.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 13, 2026 22:42
@ptr727

ptr727 commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Answering a suppressed (low-confidence) finding not in any thread, scripts/test_pr_review.py:2422: confirmed real. Fixed in 2d06909: switched from source.replace(' ', '') to ''.join(source.split()), which strips every whitespace variant (tabs, newlines) rather than only spaces, so a reformatted 'union:\nfalse' can no longer slip past the guard undetected.

Also re-answering the round-2 suppressed finding (scripts/pr_review.py:40, the stale 'only writing path' claim), which is showing again from its earlier round per this tool's own no-retirement behavior for suppressed findings: already fixed in bbe8c87 and answered in #685 (comment), nothing further to do there.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 034b846 into develop Aug 13, 2026
7 checks passed
@ptr727
ptr727 deleted the feature/pr-review-wait-auto-request branch August 13, 2026 22:46
ptr727 added a commit that referenced this pull request Aug 15, 2026
…Python CI Gates (#718)

Thirty-one squashes, `56f4d7d..d54862a`. 115 files, +20436/-5298.

**Merge with a merge commit, never a squash, and never with
`--delete-branch`.** This pull request's head is `develop` itself.

## What lands

**Fleet Skills.** The `.agents/skills/` source tree, the generated
`.claude-plugin/` distribution, `scripts/build_dist.py` with its
`--check` gate, and `scripts/skills_install.py` with its host stamp
(#676). Packaged as skills on top of the scaffold: PR review conduct and
Copilot instructions upkeep (#677), comment and doc style (#678),
resync-a-repo and fleet-conformance-check (#679), the per-language
codestyles (#680), git commit conventions and operational vs release
workflow (#681), stand up a repo (#683), and repo-worktree (#717).
Coverage gaps closed in three passes (#690, #691, #692) plus the P4
sentence-length opt-in (#697).

**Host setup.** The Windows host-setup tooling and its PowerShell gate
(#674), the Windows bootstrap loader (#682), Docker install and upgrade
on Linux and Windows with a version floor (#701, #705), a `uv` floor in
`spec/host-tools.json` (#698), self-healing of a shadowing `uv`, `jq`,
or `git-restore-mtime` copy (#689), node's real winget package id
(#696), and a README for the Linux host-setup nuances (#710).

**Python and CI.** Python tooling in CI with the script tests moved to
`scripts/tests` (#704), `ruff format` adopted and gated (#709), and the
PSScriptAnalyzer claim conditioned on repos that carry `.ps1` files
(#686).

**Conduct rules.** Triage-order and scope guardrails in
pr-review-conduct (#684), `pr_review.py wait` requesting a review rather
than only polling for one (#685), a tech-agnostic signed-commit
verification (#708), execution rather than analogy to verify
platform-specific code (#715), and a unique worktree for every task
(#717).

**Docs.** The fleet map and gap register with peer messaging declared
(#687), mermaid flow diagrams in the kept-authority docs (#702), and the
map pointed at the shipped diagrams and current tooling (#703).

## Issues this promotion closes

Each landed on `develop` on its own pull request. The keyword fires only
on a merge into `main`, so it sits here rather than on the feature pull
requests.

Closes #700
Closes #707
Closes #711
Closes #712
Closes #714
Closes #688

#699 stays open on purpose: #717 shipped the layout convention and the
skill, and the physical migration of existing checkouts is still tracked
there.

## Review record

Every squash closed its own Copilot loop on its own pull request before
merging to `develop`. This promotion carries no new content of its own,
so its review is the merged tree as a whole.

## Consequence worth stating

The `GOVERNANCE.md` and `AGENTS.md` sections these squashes changed
become the canonical the moment this reaches `main`, and every carrying
repository reads as drifted from that point until it resyncs. That is
the ordinary consequence of a canonical moving rather than a defect. The
Skills installer added here is also how a machine picks the new skills
up, so a session that keeps restating a rule already packaged as a skill
is the signal to run it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants