chore: bulk-prune 41 merged-but-undeleted branches + investigate auto-delete root cause #154
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#154
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The phenomenon
Operator surfaced 2026-06-27: 41 merged work branches remain on
origindespite the repo'sdefault_delete_branch_after_merge: truesetting. The auto-delete isn't firing on merges.Repo settings (verified via API)
Suspected root cause: Forgejo's auto-delete-after-merge may not fire when the merge style is
fast-forward-only. The auto-delete logic is documented for regular merges + squash merges; fast-forward-only's behavior wrt branch deletion is less well-documented and behaves inconsistently across Forgejo versions.Inventory at 2026-06-27 01:38 UTC
41 orphan branches, grouped by class:
Self-bootstrap re-pin branches (11)
One per re-pin cycle this session + earlier:
i/self-bootstrap-v0.5.0-rc.1i/self-bootstrap-v0.5.1-rc.1i/self-bootstrap-v0.6.1-rc.1i/self-bootstrap-v0.6.2-rc.1i/self-bootstrap-v0.8.0-rc.1i/self-bootstrap-v0.9.0-rc.1i/self-bootstrap-v0.10.0-rc.1i/self-bootstrap-v0.10.1-rc.1i/self-bootstrap-v0.10.2-rc.1i/self-bootstrap-v0.10.3-rc.1i/self-bootstrap-v0.10.4-rc.1v0.10.x sprint branches (5)
Today's work:
i/v0.10.x-cut-cancel-noise-139i/v0.10.x-idempotency-pre-checksi/v0.10.x-manifest-precheck-remote-awarei/v0.10.x-self-bootstrap-backstop-124i/v0.10.x-test-seams-docs-sprintOlder feature/sprint branches (~24)
docs/readme-refresh-108i/112-review-gate-tradeoff-docsi/52-slice-1b-impl,i/52-slice-2,i/52-slice-5-docs,i/52-v0.4.0-narrative-fragmenti/66-prerelease-tag-stable-detectioni/70-dry-run-truthy-checki/73-git-identity-update-pathi/77-fragment-frontmatteri/78-manifest-via-pri/86-87-64-layer2-race-stale-cleanupi/92-head-label-layer2i/98-adr-0006-decision-ci/adr-0007-direct-push-manifesti/agents-md-arc-convergencei/agents-md-axis-a-fifth-instancei/rc.2-rebump-after-70-fixi/rc.3-rebump-after-73-fixi/v0.6.2-substrate-sweepi/v0.8.1-sprinti/v0.9.0-label-bump-controli/v0.9.1-publish-mode-default-flipi/v1.0-readiness-audit-pass-1i/v1.0-readiness-audit-pass-2-drop-v03xTotal
41 branches — all merged + no longer referenced.
Proposed cleanup
(A) Historical: bulk-delete the 41 orphans now
Forgejo API
DELETE /repos/{owner}/{repo}/branches/{branch}for each. Safe because:Verification protocol per branch before delete:
git merge-base --is-ancestor <branch> main→ confirms mergedhead.ref(via Forgejo API)Could be a small shell script
scripts/prune-merged-branches.shthat does the audit + delete, or just a one-shot API loop.(B) Going forward: investigate auto-delete root cause
Two paths:
File a Forgejo upstream issue documenting the empirical observation: "default_delete_branch_after_merge: true + default_merge_style: fast-forward-only → branches survive after merge." Get upstream fix.
Workaround within the toolkit: add a step to the release-toolkit's own workflows (or a separate housekeeping workflow) that runs nightly + prunes merged branches automatically. Independent of upstream.
My lean: (B-1) file upstream + (B-2) ship the housekeeping workflow as a backstop. The upstream fix is the right long-term, but the workflow gives us immediate self-healing.
(C) Optional: a re-usable consumer-side workflow
The same housekeeping logic could ship as a reusable workflow (
_prune-merged-branches.yml) that consumers opt into. Avoids the same problem on cellblock + future adopters. Probably out of scope for THIS tracker; could file as a follow-up if there's appetite.Composition
Implementation surface
For (A) historical prune
scripts/prune-merged-branches.sh: ~30 lines that lists merged branches not referenced by any open PR + deletes via API. Idempotent.For (B) workaround / housekeeping workflow
.forgejo/workflows/housekeeping.yml(toolkit-self only): scheduled (e.g., weekly) or workflow_dispatch trigger; invokes the prune scriptEstimated combined scope: ~80 lines + 1 PR cycle.
What this PR does NOT do
release-prep/rollingbranch — that's the stable rolling-PR branch identity; load-bearing per the cut mechanicRefs
fast-forward-onlymerge style (worth filing upstream)Additional option (D): change
default_merge_styleto one that triggers auto-deleteOperator engagement 2026-06-27: linear history preserved (no merge commits / no parallel code lines) is the load-bearing constraint; the specific merge strategy is open.
Forgejo's merge styles that preserve linear history:
fast-forward-only(current)rebasesquashmergerebase-mergeTrade-offs
rebase: preserves the per-commit history of each PR but commits get new SHAs on main. Functionally similar to ff-only for our use; if it triggers auto-delete, it's the lowest-friction switch.squash: cleanest main history (one commit per PR). Loses intra-PR commit granularity, which matters for PRs with meaningful intermediate states (review-folds, mutation-verification steps, etc.). Our recent PRs are mostly single-commit anyway — looking at #144/#147/#143 etc. they have 1-2 commits each — so the granularity loss would be modest. Auto-delete generally documented to work with squash.Empirical probe to inform the choice
Could file a test PR on a throwaway branch + try each style + observe which triggers auto-delete. Bounded probe (~5 minutes per style).
My revised lean
If empirical probe shows
rebasetriggers auto-delete → switch torebase. Minimal behavioral change vs ff-only; preserves per-commit history; gets the auto-delete win.If only
squashtriggers it → switch tosquash+ accept the per-PR granularity loss. Most PRs are single-commit anyway; the loss is modest.Either way: the merge-strategy switch becomes the substantive fix; the housekeeping workflow becomes the BACKSTOP for any future regression. Both layers + (A) historical prune covers the full surface.
Composition update
If (D) ships:
This option strictly improves on (B) by addressing the cause rather than the symptom + simplifies the toolkit's own workflow surface.
Sprint 3 closeout — empirical findings + actions taken
Probe (Part B-1 root cause investigation)
Empirical observation: rebase merge-strategy also doesn't auto-delete branches on this Forgejo instance.
Evidence from this session's merges via
mcp__forgejo__merge_pull_request(merge_method=rebase):i/cleanup-pre-v1-unicode-keepi/re-pin-v0.11.1-rc.1i/153-rc-tag-lifecyclei/164-self-pin-exclusioni/re-pin-v0.12.0-rc.1i/170-cleanup-fragmentsHypothesis revision: the bug is NOT specific to
fast-forward-onlymerge-strategy. It applies to API-driven merges in general — the repo settingdefault_delete_branch_after_merge: trueis not honored when merges happen via thePOST /repos/.../pulls/N/mergeendpoint, regardless of theDovalue.Hint from substrate inspection: the Forgejo MCP wrapper's
merge_pull_requestschema does NOT expose adelete_branch_after_mergeparameter (verified via the tool's JSONSchema). The underlying Forgejo API accepts the field in the request body; the wrapper omits it. Best-guess explanation:delete_branch_after_mergedefaults tofalsewhen not explicitly set in the request body (contradicting the documented "defaults to repo setting" claim).Both fit the observed evidence. A definitive answer needs:
delete_branch_after_merge: trueto confirm the API DOES support it.Deferred to follow-up — the bulk-prune doesn't depend on the answer.
Action (Part A bulk-prune) — DONE
Bulk-pruned 48 orphan branches via direct
DELETE /repos/.../branches/{branch}calls:Remote now holds 2 branches:
main+release-prep/rolling(the stable rolling-PR identity).Action (Part B-2 upstream filing) — substrate-honest deferral
Filed as
alcatraz-infratracker rather than codeberg.org upstream, since:Will surface to upstream Forgejo if the alcatraz-infra investigation confirms the API-merge default-ignoring behavior is genuine Forgejo bug.
Action (Part B-2 housekeeping workflow) — held
The
.forgejo/workflows/housekeeping.ymlproposal from #154's body would be the durable self-healing mechanism. Holding for now because:Foldable into a future cleanup wave or filed as a standalone follow-up if MCP-merge gap closure doesn't pan out.
Composition
Closing
Closing this tracker. The bulk-prune is done; the upstream investigation continues on a separate alcatraz-infra tracker; the housekeeping-workflow is held pending the MCP-merge gap investigation.