Skip to content

repo-fleet-hygiene: per-repository REST merged-PR queries truncate and force a privacy gate; one aliased GraphQL query retires both finding kinds #2604

Description

@kyle-sexton

Context: #2597

Problem

Merge evidence is gathered with one gh pr list --state merged REST query per repository, matched locally by branch name. That approach has two costs the current design then has to work around:

  1. It truncates on high-traffic repositories, producing merged-pr-window-truncated and rendering every absent merged finding in that repository unproven.
  2. Because the batch cannot answer for a branch it missed, an exact per-branch --head fallback is needed — and because that transmits a local branch name to github.com, it is privacy-gated, producing merge-evidence-privacy-gated and leaving branches with no verdict at all.

A single aliased GraphQL query removes both. Neither finding kind needs to exist.

Evidence

Measured live against the GitHub GraphQL API:

query {
  rateLimit { cost nodeCount }
  b0: repository(owner:"", name:"") {
    pullRequests(headRefName:"branch-a", first:1, states:[MERGED]) {
      nodes { number headRefName headRefOid mergedAt state } } }
  b1: repository(owner:"", name:"") { pullRequests(headRefName:"branch-b", …) { … } }
}
  • 1 branch → cost: 1
  • 40 branches → cost: 1, nodeCount: 40, all 40 aliases returned

Against a documented ceiling of 500,000 nodes per call, first/last ∈ 1–100, and a 5,000-point/hour primary limit. headRefName is an exact match.

By contrast, on the fleet measured: the REST approach capped claude-code-plugins at its 1000-PR window while 1403 merged PRs exist, and gated 102 branches in one repository and 27 in another. (Verification separately confirmed the 403 PRs outside the window contained no match for any unflagged branch, so the realized impact on that run was zero — but the mechanism is real and the report correctly said so.)

The privacy gate's stated remedy — a per-branch gh pr list --head for each gated branch — is 102 manual network round-trips in the worst case observed. That is not a remedy anyone will execute.

Also worth recording, since it is the obvious alternative and is wrong: the search API's head: qualifier matches "pull requests opened from branch names beginning with the word" — prefix semantics that would treat feature/auth-v2 as merged because feature/auth merged. Only GraphQL's headRefName argument is exact.

Proposed change

  • Replace the per-repository REST merged-PR crawl with one aliased GraphQL query per repository, batching branches (100 per alias page, well within limits).
  • Retire merged-pr-window-truncated — a query that answers per-branch does not have a window to exhaust.
  • Retire merge-evidence-privacy-gated — the query transmits no local branch name that is not already a branch the tool is asking about on the operator's own repository, and the whole-repository form transmits none at all. If a privacy posture is still wanted, make it a configuration choice rather than a silent gate that leaves branches unverdicted.
  • Never use the search API's head: qualifier for branch matching.

Acceptance criteria

  • A repository with more merged PRs than the old window returns complete merge evidence.
  • No finding kind reports that merge evidence was withheld for branches the operator asked about.
  • Branch matching is exact; feature/auth and feature/auth-v2 never conflate.
  • The rate cost per repository is documented and bounded.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions