From 1441aa6936c82f67a2f1935bdd83105b1020ef69 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 1 Feb 2026 09:57:36 -0500 Subject: [PATCH 1/3] Fix worktree-rm branch deletion: check GitHub PR merge status before deleting git branch -d fails to detect squash-merged branches because the commit hashes differ. Replace the single git branch -d with a 3-step approach: 1. Fetch origin to update tracking refs (best-effort) 2. Query GitHub via gh pr view for PR merge status 3. Force-delete (-D) when GitHub confirms merge; safe-delete (-d) otherwise This handles squash merges, rebase merges, and stale local refs while degrading gracefully when gh is unavailable or offline. Co-Authored-By: Claude Opus 4.5 --- .claude/commands/worktree-rm.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/.claude/commands/worktree-rm.md b/.claude/commands/worktree-rm.md index 5102ddcc..25b02da9 100644 --- a/.claude/commands/worktree-rm.md +++ b/.claude/commands/worktree-rm.md @@ -83,14 +83,37 @@ Only attempt branch deletion if `$BRANCH` equals `` (meaning we created it via `/worktree-new ` without a base-ref). If the branch is something else (e.g., `feature/existing-branch`), skip deletion — the user didn't create it. +#### 6a. Fetch origin (best-effort) + +```bash +git fetch origin --quiet 2>/dev/null || true +``` + +This updates tracking refs so `git branch -d` has better merge detection. Scoped +to `origin` to avoid fetching all remotes. Fails silently if offline or no remote. + +**If this step hangs** (network issues), Ctrl-C and continue — it is not required. + +#### 6b. Check GitHub PR status + ```bash -git branch -d -- "$BRANCH" +gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null ``` -Let the output print naturally: -- If the branch was merged, it will be deleted and git prints a confirmation. -- If not fully merged, git prints a warning — relay that to the user - (suggest `git branch -D -- "$BRANCH"` if they want to force-delete). +Interpret by checking the **exit code first**, then the output: + +1. Command **exits non-zero** (gh not installed, no PR exists, auth/network error) → `PR_MERGED=unknown` +2. Command **exits zero** and output is `MERGED` → `PR_MERGED=true` +3. Command **exits zero** and output is `OPEN` or `CLOSED` → `PR_MERGED=false` + +#### 6c. Delete the branch + +- **`PR_MERGED=true`**: Force-delete with `git branch -D -- "$BRANCH"` — GitHub + confirms the work is merged (handles squash merges, rebase merges, etc.). + Report: "Branch `$BRANCH` deleted (PR was merged on GitHub)." +- **`PR_MERGED=false` or `unknown`**: Safe-delete with `git branch -d -- "$BRANCH"`. + If it fails because the branch is not fully merged, relay the warning and + suggest `git branch -D -- "$BRANCH"` if they want to force-delete. ### 7. Report From 2628482c53fa9856697307dc31657e02e92e2bd0 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 1 Feb 2026 10:04:59 -0500 Subject: [PATCH 2/3] Use gh pr list --head instead of gh pr view to avoid numeric branch name confusion gh pr view "$BRANCH" interprets numeric branch names (e.g., 1234) as PR numbers, which could cause force-deletion of an unrelated branch's work. Switch to gh pr list --head which always queries by branch name. Also handle the new empty-output case when no PR exists for the branch. Co-Authored-By: Claude Opus 4.5 --- .claude/commands/worktree-rm.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude/commands/worktree-rm.md b/.claude/commands/worktree-rm.md index 25b02da9..c4486be2 100644 --- a/.claude/commands/worktree-rm.md +++ b/.claude/commands/worktree-rm.md @@ -97,14 +97,18 @@ to `origin` to avoid fetching all remotes. Fails silently if offline or no remot #### 6b. Check GitHub PR status ```bash -gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null +gh pr list --head "$BRANCH" --state all --json state --jq '.[0].state' 2>/dev/null ``` +Note: `gh pr list --head` queries by branch name, avoiding the `gh pr view` pitfall +where numeric branch names (e.g., `1234`) are interpreted as PR numbers. + Interpret by checking the **exit code first**, then the output: -1. Command **exits non-zero** (gh not installed, no PR exists, auth/network error) → `PR_MERGED=unknown` +1. Command **exits non-zero** (gh not installed, auth/network error) → `PR_MERGED=unknown` 2. Command **exits zero** and output is `MERGED` → `PR_MERGED=true` 3. Command **exits zero** and output is `OPEN` or `CLOSED` → `PR_MERGED=false` +4. Command **exits zero** but output is **empty** (no PR exists for this branch) → `PR_MERGED=unknown` #### 6c. Delete the branch From 2d6c556addac1cde485729edbb4d044064d4acb2 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 1 Feb 2026 10:12:07 -0500 Subject: [PATCH 3/3] Address AI review: safer branch deletion in worktree-rm Use --state merged for unambiguous multi-PR handling and add upstream-ahead check to prevent force-deleting local-only commits. Co-Authored-By: Claude Opus 4.5 --- .claude/commands/worktree-rm.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/.claude/commands/worktree-rm.md b/.claude/commands/worktree-rm.md index c4486be2..4b2dba7e 100644 --- a/.claude/commands/worktree-rm.md +++ b/.claude/commands/worktree-rm.md @@ -97,25 +97,35 @@ to `origin` to avoid fetching all remotes. Fails silently if offline or no remot #### 6b. Check GitHub PR status ```bash -gh pr list --head "$BRANCH" --state all --json state --jq '.[0].state' 2>/dev/null +gh pr list --head "$BRANCH" --state merged --json number --jq '.[0].number' 2>/dev/null ``` Note: `gh pr list --head` queries by branch name, avoiding the `gh pr view` pitfall where numeric branch names (e.g., `1234`) are interpreted as PR numbers. +Using `--state merged` directly queries for merged PRs, avoiding ambiguity when +multiple PRs share the same head branch (e.g., one open and one merged). + Interpret by checking the **exit code first**, then the output: -1. Command **exits non-zero** (gh not installed, auth/network error) → `PR_MERGED=unknown` -2. Command **exits zero** and output is `MERGED` → `PR_MERGED=true` -3. Command **exits zero** and output is `OPEN` or `CLOSED` → `PR_MERGED=false` -4. Command **exits zero** but output is **empty** (no PR exists for this branch) → `PR_MERGED=unknown` +1. Command **exits non-zero** (gh not installed, auth/network error) → `PR_MERGED=false` +2. Command **exits zero** and output is **non-empty** (a merged PR exists) → `PR_MERGED=true` +3. Command **exits zero** and output is **empty** (no merged PR for this branch) → `PR_MERGED=false` #### 6c. Delete the branch -- **`PR_MERGED=true`**: Force-delete with `git branch -D -- "$BRANCH"` — GitHub - confirms the work is merged (handles squash merges, rebase merges, etc.). - Report: "Branch `$BRANCH` deleted (PR was merged on GitHub)." -- **`PR_MERGED=false` or `unknown`**: Safe-delete with `git branch -d -- "$BRANCH"`. +- **`PR_MERGED=true`**: Check for unpushed local commits before force-deleting: + ```bash + UPSTREAM=$(git rev-parse --abbrev-ref "$BRANCH@{u}" 2>/dev/null) + ``` + - If upstream exists, compute `AHEAD=$(git rev-list --count "$UPSTREAM..$BRANCH")`. + - `AHEAD == 0`: Force-delete with `git branch -D -- "$BRANCH"`. + Report: "Branch `$BRANCH` deleted (PR was merged on GitHub)." + - `AHEAD > 0`: Safe-delete with `git branch -d -- "$BRANCH"`. If it fails, + warn about unpushed local commits and suggest `git branch -D -- "$BRANCH"`. + - If no upstream: Safe-delete with `git branch -d -- "$BRANCH"`. If it fails, + warn about unpushed local commits and suggest `git branch -D -- "$BRANCH"`. +- **`PR_MERGED=false`**: Safe-delete with `git branch -d -- "$BRANCH"`. If it fails because the branch is not fully merged, relay the warning and suggest `git branch -D -- "$BRANCH"` if they want to force-delete.