Skip to content

ci(rebase-stack): own branch deletion (require auto-delete off)#263

Merged
behinddwalls merged 2 commits into
mainfrom
preetam/rebase-stack-fix
Jun 17, 2026
Merged

ci(rebase-stack): own branch deletion (require auto-delete off)#263
behinddwalls merged 2 commits into
mainfrom
preetam/rebase-stack-fix

Conversation

@behinddwalls

@behinddwalls behinddwalls commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Why?

The "Rebase Stacked PRs" job was silently no-opping: with the repo's "Automatically delete head branches" setting on, GitHub deleted the merged head branch on merge, which auto-retargeted every child PR's base from the merged head branch to the merged base (e.g. main) before the job ran. Its child lookup (gh pr list --base <merged-head>) then found nothing, so it rebased nothing and still went green. Run #257 (and #256 before it) did exactly this, leaving #258/#259/#260 with doubled diffs that had to be rebased by hand.

The repo setting is now off, making this workflow the sole owner of head-branch cleanup. But that exposed a second gap: the job only deleted the merged branch at the end of its own run. When a child rebase conflicts, the run intentionally keeps the merged branch (the child still bases on it) and never fires again — so after the author manually rebases and retargets the child, nothing ever deletes the orphaned merged branch.

What?

  • Document the hard requirement that "Automatically delete head branches" stays off, with the retarget-race rationale, in the workflow header.
  • Replace the outcome-gated single-branch delete with an invariant-based sweep, cleanup_orphaned_merged_branches, that runs on every invocation: for each recently merged PR whose head branch still exists, delete it iff no open PR references it as a base or head. This:
    • deletes the just-merged branch on a clean run (children retargeted away),
    • keeps it when an immediate child rebase failed (that child still bases on it — deleting it would retarget the child to main and recreate the broken diff),
    • reaps branches stranded by an earlier conflicted run on the next merge, once their children were manually fixed.
  • Branch existence is snapshotted in one git ls-remote; the merged base (e.g. main) is never touched. Conflicted runs also emit a ::warning:: for visibility while still exiting non-fatally, and no-stack merges log a clear line.

Test Plan

  • yaml.safe_load parses the workflow.
  • bash -n on the extracted run: script.
  • ✅ Simulated the sweep loop with mock data (dedup, skip-gone, skip-base, consider-delete all behave correctly).
  • Post-merge: the next Rebase Stack run should log child rebases (or "No open child PRs … nothing to rebase"), then a sweep that deletes the merged branch when nothing depends on it.

@behinddwalls behinddwalls marked this pull request as ready for review June 17, 2026 18:25
@behinddwalls behinddwalls requested a review from a team as a code owner June 17, 2026 18:25
behinddwalls and others added 2 commits June 17, 2026 11:42
## Summary

### Why?

The "Rebase Stacked PRs" job silently no-ops when the repo's "Automatically delete head branches" setting is on. On merge, GitHub deletes the merged head branch, which auto-retargets every child PR's base from the merged head branch to the merged base (e.g. main) *before* the job runs. The job discovers children via `gh pr list --base <merged-head>`, which then returns nothing — so it rebases nothing, force-pushes nothing, and still reports success. Run #257 (and #256 before it) did exactly this, leaving the downstream stack (#258/#259/#260) with doubled diffs that had to be rebased by hand.

The fix is to keep "Automatically delete head branches" off (now done at the repo level) and let this workflow own head-branch cleanup. With the setting off, GitHub leaves child bases untouched, the lookup finds them, and the job rebases the stack and then deletes the merged branch itself — for both stacked and non-stacked PRs.

### What?

- Document the hard requirement that "Automatically delete head branches" stays off, with the retarget-race rationale, in the workflow header.
- Log a clear line when a merge has no child PRs instead of returning silently.
- Clarify the branch-deletion step: it runs on every merged PR (stacked or not) and is skipped only when a child rebase failed; fix the misleading "All stacked PRs rebased successfully" message that printed even on no-op runs.

No behavior change on the happy path — the job already deleted the merged branch on success; this makes the intent explicit and the logs legible.

## Test Plan

- ✅ `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/rebase-stack.yml'))"` — YAML parses.
- Confirmed `delete_branch_on_merge` is `false` via `gh api repos/uber/submitqueue`.
- Post-merge: the next Rebase Stack run should log child rebases or "No open child PRs … nothing to rebase", then "Deleting merged branch …" and actually remove it.

Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary

### Why?

With "Automatically delete head branches" off, this workflow owns branch cleanup — but it only deleted the merged branch at the end of its own run. When a child rebase conflicts, the run intentionally keeps the merged branch (the child still bases on it) and never fires again. After the author manually rebases the child and retargets it to main, nothing deletes the now-orphaned merged branch, so merged branches linger forever.

### What?

Replace the outcome-gated single-branch delete with an invariant-based sweep, `cleanup_orphaned_merged_branches`, that runs on every invocation: for each recently merged PR whose head branch still exists, delete it iff no open PR references it as a base or head. This deletes the just-merged branch on a clean run, keeps it when an immediate child rebase failed, and reaps branches stranded by earlier conflicted runs on the next merge once their children were fixed. Branch existence is snapshotted in one `git ls-remote`; the merged base (e.g. main) is never touched.

Conflicted runs now also emit a `::warning::` for visibility while still exiting non-fatally.

## Test Plan

- ✅ `yaml.safe_load` parses the workflow.
- ✅ `bash -n` on the extracted `run:` script.
- ✅ Simulated the sweep loop with mock data (dedup, skip-gone, skip-base, consider-delete all behave).

Co-authored-by: Cursor <cursoragent@cursor.com>
@behinddwalls behinddwalls force-pushed the preetam/rebase-stack-fix branch from ab5584a to 061c028 Compare June 17, 2026 18:42
@behinddwalls behinddwalls merged commit 76c316b into main Jun 17, 2026
15 checks passed
@behinddwalls behinddwalls deleted the preetam/rebase-stack-fix branch June 17, 2026 18:51
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