feat: auto-delete worktrees on PR merge #199
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/claude-custom!199
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "194-post-merge-worktree-cleanup"
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?
Summary
Extends both post-merge hooks to automatically remove worktrees for merged branches. After the existing fast-forward logic, each hook extracts the merged branch name and walks
git worktree list --porcelainto find and remove the matching worktree. Prevents stale worktree accumulation (700MB incident on 2026-03-13).Changes
hooks/post-merge-rebase.sh— Added worktree cleanup after fast-forward. Extracts PR number fromgh pr mergecommand, usesgh pr viewto resolve head branch name, walks worktrees, removes match viagit worktree remove --force+git branch -D.hooks/post-mcp-merge-rebase.sh— Added worktree cleanup after fast-forward. Extracts owner/repo/pull_number from MCP tool_input, uses Forgejo API (token from$HOME/secrets/pal-e-services/forgejo.env) to resolve head branch, same worktree walk and cleanup.Test Plan
bash -nsyntax check passes on both filesReview Checklist
Related Notes
pal-e-platform/docs/superpowers/specs/2026-03-28-worktree-lifecycle-enforcement-design.mdproject-pal-e-agency— agent infrastructure projectPR Review: #199 — auto-delete worktrees on PR merge
Acceptance Criteria Check
gh pr viewfor head branchgit worktree list --porcelain, finds worktree on merged branchgit worktree remove --forcegit branch -D[post-merge-cleanup] Removed worktree for branch <name> at <path>Code Quality
forgejo.envonly when needed, and guards on both$FORGEJO_TOKENand$FORGEJO_URLbefore making the API call.cds to$CWDbefore callinggh pr view, ensuring the gh CLI resolves the correct repo context.[ -n "$MERGED_BRANCH" ],[ -n "$worktree_path" ]), making failure silent.Test Results (from PR body)
bash -nsyntax validation: PASS on both filesFindings
No issues found. Implementation matches the spec exactly with no scope creep.
VERDICT: APPROVE
PR #199 Review
DOMAIN REVIEW
Tech stack: Bash shell hooks (Claude Code PostToolUse hooks),
jqfor JSON parsing,curlfor Forgejo API,ghCLI for GitHub/Forgejo CLI.What this PR does: After a successful PR merge (via either
gh pr mergeormcp__forgejo__merge_approved_pr), the hooks now resolve the merged branch name and walkgit worktree list --porcelainto find and--forceremove the matching worktree, then delete the local branch. This addresses the 700MB stale worktree accumulation problem from 2026-03-13.The approach is sound. Both hooks already run after confirmed successful merges, so the cleanup logic triggers at the right time. Error handling is fail-open (all git/curl commands redirect stderr to
/dev/null), which is correct for a PostToolUse hook -- cleanup failures must never block agent workflow.BLOCKERS
1. DRY violation: worktree walk-and-remove logic duplicated verbatim across two hooks
The worktree parsing block (the
while IFS= read -r lineloop, thecasestatement, the trailing-entry handler, and thegit worktree remove --force+git branch -Dcleanup) is identical in bothpost-merge-rebase.shandpost-mcp-merge-rebase.sh-- approximately 30 lines copy-pasted.This codebase already has a pattern for shared logic:
forgejo-helper.sh(sourced byboard-item-on-merge.shand others). The worktree walk-and-remove block should be extracted into a shared helper function (e.g., in aworktree-helper.shor added to an existing shared file) and sourced by both hooks.Note:
cleanup-worktrees.shalso has a very similar porcelain-parsing loop (with age-based filtering). That is a pre-existing DRY issue outside this PR's scope, but it reinforces the need for a shared function now rather than tripling the duplication.Why this is a blocker, not a nit: The worktree walk logic parses a specific porcelain format. If that format changes or a bug is found in the parser, fixing it in one hook but not the other creates silent divergence -- exactly the kind of divergence risk that DRY violations in operational paths produce. Two hooks doing the same
--forceremoval from the same parsed data must share the same code.2. Inconsistent auth pattern: token auth vs. existing basic auth convention
The new code in
post-mcp-merge-rebase.shsourcesforgejo.envdirectly and usesAuthorization: token $FORGEJO_TOKEN:Every other hook in this codebase (
board-item-on-merge.sh,label-on-verdict.sh, etc.) uses the sharedforgejo-helper.shwith_load_forgejo_credsand basic auth (-u "${FORGEJO_USER}:${FORGEJO_PASSWORD}"). This PR introduces a second, incompatible auth pattern.This is a DRY/divergence blocker for the same reason: two authentication paths to the same API will diverge when credentials rotate. Use
forgejo-helper.shand the existing basic auth pattern, or refactorforgejo-helper.shto support token auth for all consumers. Do not introduce a parallel path.NITS
1. Uninitialized
MERGED_BRANCHinpost-merge-rebase.shIn the MCP hook,
MERGED_BRANCH=""is explicitly initialized. In theghhook, it is not -- it relies on bash treating unset variables as empty in[ -n ]. This works but is inconsistent between the two hooks. Initialize it explicitly for clarity and consistency.2. Edge case:
gh pr merge --squashwithout explicit PR numberWhen
gh pr mergeis invoked without a PR number (gh infers it from the current branch), the regexgrep -oE 'gh\s+pr\s+merge\s+([0-9]+)'will not match, so cleanup silently skips. This is safe (fail-open) but worth a comment in the code noting this limitation, sincegh pr mergewithout an explicit number is a valid invocation pattern.3. Missing
--connect-timeout/--max-timeon curlThe existing
board-item-on-merge.shusescurl -s --connect-timeout 5 --max-time 10. The new curl inpost-mcp-merge-rebase.shusescurl -swithout timeouts. In a PostToolUse hook, a hanging curl blocks the agent. Add timeouts consistent with the existing pattern.4. Log tag inconsistency
Both hooks emit
[post-merge-cleanup]on successful removal. The fast-forward messages use[post-merge-rebase]and[post-mcp-merge-rebase]respectively. For log grep-ability, the cleanup messages should differentiate which hook they came from (e.g.,[post-merge-cleanup:gh]and[post-merge-cleanup:mcp]), or at minimum match the hook's existing prefix convention.SOP COMPLIANCE
194-post-merge-worktree-cleanupreferences #194Closes #1942026-03-28-worktree-lifecycle-enforcement-design.md~/secrets/PROCESS OBSERVATIONS
--forceremove is idempotent). Medium for the auth divergence -- token auth may silently fail ifFORGEJO_TOKENis not set or expires, with no fallback.cleanup-worktrees.shdependency and prevents the 700MB disk bloat that previously required manual intervention.VERDICT: NOT APPROVED
Two blockers must be addressed:
forgejo-helper.shauth pattern instead of introducing a parallel token-based auth path.PR #199 Review (Re-review)
DOMAIN REVIEW
Tech stack: Bash shell hooks (PostToolUse hooks for Claude Code), Forgejo API integration, git worktree management.
Shared function extraction (
forgejo-helper.shlines 276-319):remove_worktree_for_branch()is well-structured. It accepts three parameters (main_worktree, branch, log_tag), validates inputs with an early return, parsesgit worktree list --porcelaincorrectly, and handles the edge case where porcelain output does not end with a blank line (lines 309-312). The--forceflag onworktree removeis appropriate since merged branches have no uncommitted work. Thebranch -Dcleanup after worktree removal prevents dangling refs.Auth pattern (
post-mcp-merge-rebase.shlines 74-79): Now uses_load_forgejo_creds+-u "${FORGEJO_USER}:${FORGEJO_PASSWORD}"basic auth, matching the established pattern inforgejo_set_label,forgejo_comment_on_issue, and all other helper functions. Curl timeouts (--connect-timeout 5 --max-time 10) are consistent with existing conventions.GH CLI path (
post-merge-rebase.shlines 71-78): Thegh pr viewcall to resolve the head branch name is appropriate. The NOTE comment (lines 72-73) documenting the limitation whengh pr mergeis invoked without an explicit PR number is good defensive documentation.Error handling: Both hooks initialize
MERGED_BRANCH=""before conditionals, soremove_worktree_for_branchreceives an empty string on failure paths and silently returns via the guard clause at line 285. All git and curl commands redirect stderr to/dev/nullwhere appropriate. Theexit 0at the end of both hooks ensures the hook never blocks the main merge flow.BLOCKERS
None. Both previously identified blockers are resolved:
DRY violation (RESOLVED): Worktree walk logic extracted into
remove_worktree_for_branch()inforgejo-helper.sh. Both hooks call the shared function with differentiated log tags.Inconsistent auth pattern (RESOLVED): MCP hook now uses
_load_forgejo_creds+ basic auth (-uflag). Token-based auth is fully removed -- confirmed via grep across the entire hooks directory.NITS
All four previous nits are resolved:
--connect-timeout 5 --max-time 10)MERGED_BRANCH=""before conditionalspost-merge-cleanup:ghvspost-merge-cleanup:mcpOne new minor nit:
source "${HOOK_DIR}/forgejo-helper.sh"without the# shellcheck source=forgejo-helper.shcomment thatboard-item-on-merge.shuses (line 30 of that file). Non-blocking, but would silence SC1091 if shellcheck is ever run across hooks.SOP COMPLIANCE
194-post-merge-worktree-cleanupreferences #194docs/superpowers/specs/2026-03-28-worktree-lifecycle-enforcement-design.md(verified exists) andproject-pal-e-agencyCloses #194FORGEJO_PASSWORDdefault empty string in credential loader)PROCESS OBSERVATIONS
Clean fix commit addressing all review feedback. The extraction of
remove_worktree_for_branch()intoforgejo-helper.shis the right architectural choice -- it sits alongside the other shared Forgejo functions and can be reused by future cleanup hooks. The auth pattern is now fully consistent across the entire helper library.The manual test plan item (actual worktree removal after real PR merge) is still unchecked. This is expected -- it can only be validated post-merge with a real worktree present. Recommend Lucas validate this on the next PR merge after this lands.
VERDICT: APPROVED