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:
- A pull request exists and is MERGED (not merely closed) — this both proves the work landed and is what creates the restore path.
- The merged PR's
headRefOid equals the current remote ref tip — no post-merge pushes.
- No open PR references the branch, as head or base.
- The branch is not protected and not matched by a ruleset "Restrict deletions".
- It is not the repository's default branch.
- 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.
Context: #2597
Problem
Nothing in the plugin fleet cleans up merged remote branches.
source-control:worktreestates plainly that it "does not manage remote branches — GitHub'sdelete_branch_on_mergehandles remote cleanup on merge (when enabled); localgit branch -Dis emitted for the user, never run inline."repo-fleet-hygieneaudits 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_mergeis evidently not enabled everywhere: 10 repositories reportedmerge-evidence-privacy-gatedfor 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:
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)returnsheadRefNameplusrepository.nameWithOwnerat 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:
headRefOidequals the current remote ref tip — no post-merge pushes.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-runfor 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:
The durable fix is upstream of all of this.
delete_branch_on_mergestops the class from accruing.source-control:worktree auditalready 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-hocghcall — the audit should report the gap and name the owning repository, not attempt the change.Acceptance criteria
git push --delete --dry-runoutput is the preview.delete_branch_on_mergeis disabled, and routes the fix to the settings-owning repository rather than performing it.