Skip to content

repo-fleet-hygiene: no merged-remote-branch capability, and no plugin in the fleet owns one #2607

Description

@kyle-sexton

Context: #2597

Problem

Nothing in the plugin fleet cleans up merged remote branches. source-control:worktree states plainly that it "does not manage remote branches — GitHub's delete_branch_on_merge handles remote cleanup on merge (when enabled); local git branch -D is emitted for the user, never run inline." repo-fleet-hygiene audits local branches and worktrees only.

The result is that a fleet can be cleaned locally while merged branches accumulate on the remote indefinitely.

Evidence

On the fleet measured, delete_branch_on_merge is evidently not enabled everywhere: 10 repositories reported merge-evidence-privacy-gated for branches absent from the local remote-tracking inventory, and multiple repositories showed merged local branches whose remote counterparts had already been auto-deleted — i.e. inconsistent behaviour across the fleet, which is what an unenabled-per-repository setting looks like.

Relevant documented facts, all from GitHub's own documentation:

  • "Automatically delete head branches" is admin-only and not enabled by default.
  • Branch protection rules and rulesets can prevent automatic deletion; the ruleset mechanism is "Restrict deletions".
  • Deletion is permitted when "the pull request has been merged or closed and there are no other open pull requests referencing the branch."
  • Restoration is documented only for "the head branch of a closed pull request", with a condition rather than a time bound. A remote branch that never had a PR has no documented restore path at all.

An org-wide author-scoped enumeration is available in one call and was executed successfully during research: search(query:"org:X is:pr is:merged author:Y", type:ISSUE) returns headRefName plus repository.nameWithOwner at cost 1. It rides the search backend, so the 1,000-result cap and 30 requests/minute limit apply.

Proposed change

Add merged-remote-branch cleanup as its own tier with its own gate, not folded into the local pass. The risk profile differs: local branch deletion destroys the branch reflog outright, whereas a merged remote branch retains GitHub's restore path — so remote deletion of a merged branch is the more recoverable operation. What makes it distinct is that a remote ref is shared: it may be another PR's base, a CI trigger, or a deploy source. The failure mode is disruption, not data loss, and it deserves its own confirmation.

Conditions, all of which must hold before a remote branch is offered for deletion:

  1. A pull request exists and is MERGED (not merely closed) — this both proves the work landed and is what creates the restore path.
  2. The merged PR's headRefOid equals the current remote ref tip — no post-merge pushes.
  3. No open PR references the branch, as head or base.
  4. The branch is not protected and not matched by a ruleset "Restrict deletions".
  5. It is not the repository's default branch.
  6. For the default scope, the PR was authored by the operator.

Refuse outright, never offer: any remote branch with no PR in its history. There is no documented restore path for that class. Report it; do not propose deleting it.

Use git push --delete --dry-run for the preview — it is the one native dry-run available on this tier, and this is the tier where a real preview matters most.

Two scopes:

  • default — branches related to the operator's local repositories, author-scoped.
  • extended sweep — opt-in, enumerating merged branches org-wide regardless of local presence via the single search query above. Must disclose truncation: the search backend caps at 1,000 results, so a large org needs date-sliced pagination and the report must say when it truncated.

The durable fix is upstream of all of this. delete_branch_on_merge stops the class from accruing. source-control:worktree audit already checks that key as part of its configuration health; what is missing is fleet-wide reporting of it. Note that in this organization, repository settings are Pulumi-managed, so the recommendation is a change there rather than an ad-hoc gh call — the audit should report the gap and name the owning repository, not attempt the change.

Acceptance criteria

  • Merged remote branches are reported with all six conditions evaluated and shown.
  • A remote branch with no PR history is reported and never offered for deletion.
  • The tier has its own confirmation, separate from local cleanup.
  • git push --delete --dry-run output is the preview.
  • The extended sweep discloses truncation explicitly.
  • The report names repositories where delete_branch_on_merge is disabled, and routes the fix to the settings-owning repository rather than performing it.

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