Skip to content

repo-fleet-hygiene:audit remote-tracking gate suppresses 92 of 195 real merged-branch candidates #1770

Description

@kyle-sexton

Summary

repo-fleet-hygiene:audit gates its merged-PR lookup on the branch being present
in the local remote-tracking inventory. In a fleet that deletes head branches on
merge, that gate suppresses evidence for precisely the branches most likely to be
cleanup candidates. Measured on an 11-repository fleet, the audit reported 103
HIGH candidates where a direct PR join found 195 — the gate hid 92 real,
PR-evidenced candidates.

Observed behavior

Audit run over --root D:/repos, 11 canonical checkouts, 506 local branches,
gh authenticated:

  • HIGH = 103, MEDIUM = 60, LOW = 11, UNKNOWN = 5
  • The 5 UNKNOWN findings are all merge-evidence-privacy-gated, covering
    300 branches whose merged state was never queried. One repository
    accounted for 291 of them.

The finding text explains the mechanism:

Finding: merge-evidence-privacy-gated
Evidence: exact merged-PR lookup skipped for 291 branch(es) absent from the
  local remote-tracking inventory: ...
Disposition: Merged state unverified; a merged branch whose remote ref was
  auto-deleted and pruned reports no merged finding
Handoff: Push or re-fetch the branch to restore remote evidence, or verify
  manually on GitHub, then rerun

The suggested handoff does not resolve it. git fetch --all --prune cannot
restore a remote ref that was deleted upstream on merge — after fetching all 11
repositories the same branches remain absent from the remote-tracking inventory,
so a rerun reports the same gap. The only escape offered is manual GitHub
verification of 300 branches.

Why the gate is not load-bearing

The audit already has the authoritative evidence source: gh pr list --repo <repo> --state merged --head <branch>, matched on headRefOid against the local
tip. That query does not require a remote-tracking ref to exist. Presence in the
remote-tracking inventory is not a precondition for the lookup — it is only a
proxy for "this branch was once pushed", which a merged PR proves far more
directly.

Cost is not the reason either

I replaced the per-branch query with one bulk call per repository:

gh pr list --repo "melodic-software/$name" --state all --limit 4000 \
   --json number,headRefName,headRefOid,state,url

11 API calls covered all 506 branches in both directions (merged evidence and
open-PR protection). Joining locally on headRefName then comparing
headRefOid to the local tip reproduced every HIGH finding the audit made and
added 92 more. This is strictly cheaper than the current per-branch query path,
not more expensive.

Resulting classification over the same 506 branches:

Disposition Count
merged PR, headRefOid == local tip 195
local tip is an ancestor of the default branch 27
merged PR present, tip drift 132
no PR, all commits on some remote ref 53
no PR, not an ancestor, commits on no remote ref 99

Secondary finding: the ancestry lane assumes merge commits

Related, and worth stating in the report header: local-ancestry-only and any
ancestry-derived reasoning are near-inert in a squash-merging fleet. Of 506
branches only 27 were ancestors of origin/<default>, because a squash merge
rewrites the commits. Correspondingly, git rev-list --count <tip> --not --remotes reported non-zero for 410 branches — work that is fully merged but
whose exact commit objects exist nowhere on any remote.

Both readings are literally true and both are misleading if used as a deletion
predicate. A consumer following the report's tiering without knowing the fleet's
merge strategy would either preserve almost everything or believe it has 410
branches of unpushed work. The report should state which predicate it used and
that ancestry evidence degrades under squash merge.

Suggested fix

  1. Drop the remote-tracking-presence gate before the merged-PR query. Absence of
    a remote ref is expected for merged-and-deleted branches, not a reason to skip
    the lookup.
  2. Switch to one bulk gh pr list --state all call per repository and join
    locally. Cheaper than the current path and it yields open-PR protection for
    free.
  3. Keep a genuine UNKNOWN tier for real API/auth/network failures only.
  4. Name the merge strategy assumption in the report, and note that ancestry
    evidence is weak under squash merge.

Environment

  • repo-fleet-hygiene 0.7.0
  • Claude Code on Windows 11 Pro 10.0.26200, Git 2.54.0.windows.1
  • gh authenticated, 11 repositories, 506 local branches
  • Observed 2026-07-29

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

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: needs-triageDefault until a priority tier is assigned.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions