Skip to content

Conversation

@echarrod
Copy link
Contributor

@echarrod echarrod commented Dec 27, 2025

Summary

Adds support for detecting and removing worktrees with merged PRs, handling squash-merged branches which don't show as merged via git branch --merged.

Problem

When using squash merge (common in GitHub workflows), git branch --merged doesn't detect these branches as merged because the commit SHAs differ. This leaves stale worktrees that can't be cleaned up with the existing git gtr clean command.

Solution

New flags for git gtr clean:

  • --merged: remove worktrees with merged PRs (uses GitHub CLI)
  • --yes, -y: skip confirmation prompts
  • --dry-run, -n: show what would be removed without removing

The --merged flag uses gh pr list --head <branch> --state merged to detect if a branch was merged via PR, regardless of merge strategy.

Usage

# Preview what would be cleaned
git gtr clean --merged --dry-run

# Clean with confirmation prompts
git gtr clean --merged

# Clean without prompts
git gtr clean --merged --yes

Requirements

  • GitHub CLI (gh) must be installed and authenticated
  • Only works with GitHub-hosted repositories

Test plan

  • Tested with repos using squash merge
  • Verified worktrees with uncommitted changes are skipped
  • Verified worktrees with untracked files are skipped
  • Verified dry-run mode shows correct output
  • Verified branches without PRs are skipped

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added --merged to auto-remove worktrees whose pull requests are merged; supports --yes/-y for automatic confirmation and --dry-run/-n to preview actions.
    • Integrates with GitHub CLI to detect merged PRs when enabled.
  • Bug Fixes / Improvements

    • Better handling and reporting for detached HEADs, main-branch exclusion, uncommitted/untracked/empty worktrees, and skipped/removed items.
  • Documentation

    • Updated help text to document new options and behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds support for detecting and removing worktrees with merged PRs.
This handles squash-merged branches which don't show as merged via
`git branch --merged`.

New flags for `git gtr clean`:
- --merged: remove worktrees with merged PRs (uses gh CLI)
- --yes, -y: skip confirmation prompts
- --dry-run, -n: show what would be removed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@echarrod echarrod requested a review from NatoBoram as a code owner December 27, 2025 11:06
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Walkthrough

Adds a merged-mode to gtr clean with new flags --merged, --yes/-y, and --dry-run/-n; verifies gh, fetches/prunes origin, inspects worktrees and branches, queries merged PRs via gh, and (optionally) removes matching worktrees and deletes local branches with safeguards and confirmation.

Changes

Cohort / File(s) Summary
Clean command implementation
bin/gtr
Adds parsing for --merged, --yes/-y, and --dry-run/-n; extends cmd_clean() with a merged-mode flow that validates gh, runs git fetch --prune origin, iterates git worktree entries, skips detached HEAD / main repo branch / worktrees with uncommitted/untracked/empty states, queries gh pr list --state merged --head <branch>, and either simulates or performs worktree removal and local branch deletion; retains existing prune/empty-directory logic.

Sequence Diagram

sequenceDiagram
    actor User
    participant gtr as gtr
    participant Git as Local Git
    participant gh as GitHub CLI
    participant GitHub as GitHub API

    User->>gtr: gtr clean --merged [--dry-run] [--yes]
    rect rgb(235,245,255)
      Note over gtr: parse flags & validate environment
      gtr->>gh: check gh availability
      gtr->>Git: git fetch --prune origin
    end

    rect rgb(246,255,242)
      Note over gtr: iterate worktrees
      gtr->>Git: git worktree list
      loop per worktree
        gtr->>gtr: skip if detached HEAD or main-repo branch
        gtr->>Git: check uncommitted/untracked/empty
        alt clean and safe
          gtr->>gh: gh pr list --state merged --head <branch>
          gh->>GitHub: query merged PRs
          GitHub-->>gh: return results
          gh-->>gtr: merged? (yes/no)
          alt merged
            opt not dry-run
              gtr->>Git: git worktree remove <path>
              gtr->>Git: git branch -d <branch>
            end
          end
        end
      end
    end

    gtr->>User: summary (removed / skipped) or dry-run report
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop through branches, sniffing clean and neat,
I ask the CLI orchard where merged PRs meet.
A dry-run tiptoe, a gentle "yes" to part,
I tidy up the pathways with a careful heart. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature addition: a new --merged flag for the clean command to handle squash-merged PRs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 03ff84a and 9c694a5.

📒 Files selected for processing (1)
  • bin/gtr
🧰 Additional context used
📓 Path-based instructions (2)
bin/gtr

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

bin/gtr: Dispatch commands through cmd_* functions in bin/gtr (case block lines 36‑77)
Update GTR_VERSION on line 8 of bin/gtr when releasing; this affects gtr version / --version output

Global set -e in bin/gtr: guard non-critical commands with || true

list --porcelain output must remain stable for scripting purposes

Update version number in bin/gtr (line 8, GTR_VERSION constant) when releasing a new version

Files:

  • bin/gtr
{bin/gtr,lib/**/*.sh,adapters/**/*.sh}

📄 CodeRabbit inference engine (.github/instructions/testing.instructions.md)

{bin/gtr,lib/**/*.sh,adapters/**/*.sh}: All commands must exit 0 (except intentional failures) and produce expected side-effects
No unquoted path errors; spaces must be handled in file paths
Hooks must run only once per creation/removal event

Files:

  • bin/gtr
🔇 Additional comments (10)
bin/gtr (10)

672-699: LGTM! Flag parsing is well-structured.

The flag parsing correctly handles the new --merged, --yes/-y, and --dry-run/-n options with proper unknown flag validation. The structure is consistent with other commands in the script.


752-754: Good practice: Fetching with prune before PR checks.

Fetching from origin with --prune ensures the remote state is current before checking PR merge status. The graceful error handling with || log_warn is appropriate.


781-794: Excellent safety checks to prevent data loss.

The implementation correctly checks for both uncommitted changes (staged and unstaged) and untracked files before removing worktrees. This prevents accidental loss of work.


808-808: Good branch deletion strategy.

The implementation correctly tries a safe delete (-d) first, then falls back to force delete (-D) for squash-merged branches that appear unmerged locally. The || true guard properly handles failures due to set -e.


764-765: Proper handling of glob expansion.

The glob pattern expansion with "$base_dir/${prefix}"* is correctly implemented, and the -d check on line 765 safely handles cases where no directories match the pattern.


801-803: Clean dry-run implementation.

The dry-run mode correctly simulates removal without performing actual changes, with clear [dry-run] prefixes in the output. The counter tracking enables accurate summary reporting.


824-829: Clear and informative reporting.

The summary output clearly distinguishes between dry-run and actual execution, providing useful feedback with both removed and skipped counts.


1200-1204: Help text accurately documents the new functionality.

The help text clearly documents the new --merged, --yes/-y, and --dry-run/-n options with appropriate descriptions. The gh CLI requirement is properly mentioned.


8-8: Verify if the version number should be updated.

The coding guidelines specify updating GTR_VERSION when releasing. This PR adds new functionality (--merged flag with --yes and --dry-run options). If this PR is intended for release, the version should be bumped (e.g., to 2.1.0 for a minor feature addition).

Based on coding guidelines, which state: "Update GTR_VERSION on line 8 of bin/gtr when releasing; this affects gtr version / --version output"


796-821: Correct the line numbers and clarify the gh command's limitations in the --merged mode logic.

The review identifies real concerns, but references incorrect line numbers. The code in question is at lines 938-963 (in the cmd_clean --merged mode), not lines 796-821.

The valid concerns are:

  1. gh pr list --head limitations: The GitHub CLI does not support the <owner>:<branch> syntax. For worktrees created from forked repositories where the PR exists in the upstream repo but the local branch name differs, this command will fail to find the PR.

  2. Ambiguous warning message (line 961): "Skipping $branch (no merged PR found)" conflates multiple failure modes:

    • No PR exists for this branch
    • PR exists but isn't merged
    • The gh command failed silently (due to || true on line 940)

The || true guard on line 940 is correct per coding guidelines and properly prevents script exit. However, the ambiguous message could be improved to clarify that gh authentication and a GitHub-hosted repository are required: log_warn "Skipping $branch (no merged PR or not GitHub-hosted)".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
bin/gtr (1)

889-892: Platform-specific installation message.

This issue was already identified in a previous review comment. The error message suggests brew install gh, which is macOS-specific.

🧹 Nitpick comments (2)
bin/gtr (2)

939-963: Enhance error feedback when gh pr list fails.

The gh pr list command can fail for multiple reasons beyond "no merged PR exists" (e.g., not authenticated, network issues, repository not on GitHub, rate limiting). Currently, all failure modes result in an empty $pr_state and the generic message "no merged PR found" (line 961), which could mislead users about the actual problem.

Consider enhancing error handling to distinguish between different failure scenarios and provide actionable feedback.

🔎 Suggested improvement
       # Check if branch has a merged PR on GitHub
-      local pr_state
-      pr_state=$(gh pr list --head "$branch" --state merged --json state --jq '.[0].state' 2>/dev/null || true)
+      local pr_state gh_error
+      if pr_state=$(gh pr list --head "$branch" --state merged --json state --jq '.[0].state' 2>&1); then
+        : # success
+      else
+        gh_error=$?
+        if echo "$pr_state" | grep -q "authentication\|not logged in"; then
+          log_warn "Skipping $branch (gh authentication required: run 'gh auth login')"
+          skipped=$((skipped + 1))
+          continue
+        fi
+        pr_state=""
+      fi

946-952: Clarify that branch deletion is included in the removal.

The confirmation prompt on line 946 asks "Remove worktree '$branch'?" but doesn't mention that the local branch will also be deleted (line 950). This could surprise users who expect only the worktree to be removed.

🔎 Suggested improvement
-        elif [ "$yes_mode" -eq 1 ] || prompt_yes_no "Remove worktree '$branch'?"; then
+        elif [ "$yes_mode" -eq 1 ] || prompt_yes_no "Remove worktree and branch '$branch'?"; then
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9c694a5 and 76dedcf.

📒 Files selected for processing (1)
  • bin/gtr
🧰 Additional context used
📓 Path-based instructions (2)
bin/gtr

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

bin/gtr: Dispatch commands through cmd_* functions in bin/gtr (case block lines 36‑77)
Update GTR_VERSION on line 8 of bin/gtr when releasing; this affects gtr version / --version output

Global set -e in bin/gtr: guard non-critical commands with || true

list --porcelain output must remain stable for scripting purposes

Update version number in bin/gtr (line 8, GTR_VERSION constant) when releasing a new version

Files:

  • bin/gtr
{bin/gtr,lib/**/*.sh,adapters/**/*.sh}

📄 CodeRabbit inference engine (.github/instructions/testing.instructions.md)

{bin/gtr,lib/**/*.sh,adapters/**/*.sh}: All commands must exit 0 (except intentional failures) and produce expected side-effects
No unquoted path errors; spaces must be handled in file paths
Hooks must run only once per creation/removal event

Files:

  • bin/gtr
🔇 Additional comments (4)
bin/gtr (4)

814-841: LGTM! Clean flag parsing implementation.

The flag parsing follows established patterns in the codebase and properly handles both long and short forms (--yes|-y, --dry-run|-n). The unknown flag guard ensures invalid options are caught early.


906-921: LGTM! Proper filtering of worktrees.

The logic correctly:

  • Iterates through worktree directories using the configured prefix
  • Skips detached HEAD states (lines 912-916)
  • Skips the main repository branch (lines 918-921)

These checks prevent unintended modifications to the primary repository.


924-936: LGTM! Comprehensive safety checks before removal.

The implementation correctly prevents removal of worktrees with:

  • Uncommitted changes (staged or unstaged)
  • Untracked files

These safety checks protect users from accidental data loss and align with best practices for worktree management.


1356-1360: Clear documentation of new flags.

The help text effectively documents the new --merged flag and its dependencies. The mention of "uses gh CLI" informs users of the external dependency.

Consider adding a note about authentication requirements (e.g., "requires gh auth login") to help users troubleshoot, but this is optional since the runtime error message covers it.

Copy link
Collaborator

@helizaga helizaga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks sound, left some comments

- Use platform-agnostic gh install message (https://cli.github.com/)
- Add gh repo view check upfront to catch auth/network issues
- Clarify prompt mentions branch deletion along with worktree removal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
bin/gtr (1)

900-902: Add upfront validation for GitHub CLI authentication and repository.

As suggested in the previous review, adding an early check for gh repo view would catch auth/network issues before iterating worktrees, providing clearer error messages.

🔎 Proposed fix
     # Ensure gh CLI is available
     if ! command -v gh >/dev/null 2>&1; then
       log_error "GitHub CLI (gh) not found. Install from: https://cli.github.com/"
       exit 1
     fi
 
+    # Verify gh is authenticated and this is a GitHub repo
+    if ! gh repo view >/dev/null 2>&1; then
+      log_error "Not a GitHub repo or gh not authenticated (run: gh auth status)"
+      exit 1
+    fi
+
     # Fetch latest from origin
     log_step "Fetching from origin..."
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a065f6 and bb34fea.

📒 Files selected for processing (1)
  • bin/gtr
🧰 Additional context used
📓 Path-based instructions (2)
bin/gtr

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

bin/gtr: Dispatch commands through cmd_* functions in bin/gtr (case block lines 36‑77)
Update GTR_VERSION on line 8 of bin/gtr when releasing; this affects gtr version / --version output

Global set -e in bin/gtr: guard non-critical commands with || true

list --porcelain output must remain stable for scripting purposes

Update version number in bin/gtr (line 8, GTR_VERSION constant) when releasing a new version

Files:

  • bin/gtr
{bin/gtr,lib/**/*.sh,adapters/**/*.sh}

📄 CodeRabbit inference engine (.github/instructions/testing.instructions.md)

{bin/gtr,lib/**/*.sh,adapters/**/*.sh}: All commands must exit 0 (except intentional failures) and produce expected side-effects
No unquoted path errors; spaces must be handled in file paths
Hooks must run only once per creation/removal event

Files:

  • bin/gtr
🧠 Learnings (1)
📚 Learning: 2025-12-29T18:46:36.983Z
Learnt from: helizaga
Repo: coderabbitai/git-worktree-runner PR: 64
File: bin/gtr:746-750
Timestamp: 2025-12-29T18:46:36.983Z
Learning: When error messages reference missing CLI tools (such as gh for GitHub CLI), use platform-agnostic installation guidance by directing users to the official website (e.g., https://cli.github.com/) instead of platform-specific package managers (brew, apt, choco) to ensure the guidance works across all operating systems. Apply this messaging to the bin/gtr script where applicable.

Applied to files:

  • bin/gtr
🔇 Additional comments (3)
bin/gtr (3)

814-841: LGTM!

Flag parsing follows the established patterns in other commands (cmd_create, cmd_remove) with proper unknown flag handling and short aliases.


944-969: LGTM!

The merged PR detection and removal logic handles edge cases well:

  • Dry-run mode previews without changes
  • Interactive confirmation shows the branch name
  • Branch deletion uses -d then -D fallback, appropriate for squash-merged branches
  • Errors are logged without aborting the loop

1362-1366: LGTM!

Help text accurately documents the new options and notes the gh CLI dependency.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
bin/gtr (2)

960-963: Consider reducing noise for branches without merged PRs.

The log_warn "Skipping $branch (no merged PR found)" will trigger for every worktree that has an active (unmerged) PR or no PR at all. This could be noisy in repos with many active feature branches.

🔎 Potential improvement: only warn when verbose or debugging

You could silently skip branches without merged PRs and only count them, then report the total at the end:

       else
-        log_warn "Skipping $branch (no merged PR found)"
         skipped=$((skipped + 1))
       fi

The final summary already shows the skipped count, which may be sufficient.


948-955: Consider preserving stderr for troubleshooting failed removals.

Suppressing stderr with 2>/dev/null on git worktree remove hides the reason for failure. When the removal fails, users only see "Failed to remove worktree" without context.

🔎 Proposed improvement
-          if git worktree remove "$dir" 2>/dev/null; then
+          if git worktree remove "$dir"; then

Alternatively, capture and display the error:

local remove_err
if remove_err=$(git worktree remove "$dir" 2>&1); then
  # success path
else
  log_error "Failed to remove worktree: $branch"
  log_error "$remove_err"
fi
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bb34fea and 10c2270.

📒 Files selected for processing (1)
  • bin/gtr
🧰 Additional context used
📓 Path-based instructions (2)
bin/gtr

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

bin/gtr: Dispatch commands through cmd_* functions in bin/gtr (case block lines 36‑77)
Update GTR_VERSION on line 8 of bin/gtr when releasing; this affects gtr version / --version output

Global set -e in bin/gtr: guard non-critical commands with || true

list --porcelain output must remain stable for scripting purposes

Update version number in bin/gtr (line 8, GTR_VERSION constant) when releasing a new version

Files:

  • bin/gtr
{bin/gtr,lib/**/*.sh,adapters/**/*.sh}

📄 CodeRabbit inference engine (.github/instructions/testing.instructions.md)

{bin/gtr,lib/**/*.sh,adapters/**/*.sh}: All commands must exit 0 (except intentional failures) and produce expected side-effects
No unquoted path errors; spaces must be handled in file paths
Hooks must run only once per creation/removal event

Files:

  • bin/gtr
🧠 Learnings (1)
📚 Learning: 2025-12-29T18:46:36.983Z
Learnt from: helizaga
Repo: coderabbitai/git-worktree-runner PR: 64
File: bin/gtr:746-750
Timestamp: 2025-12-29T18:46:36.983Z
Learning: When error messages reference missing CLI tools (such as gh for GitHub CLI), use platform-agnostic installation guidance by directing users to the official website (e.g., https://cli.github.com/) instead of platform-specific package managers (brew, apt, choco) to ensure the guidance works across all operating systems. Apply this messaging to the bin/gtr script where applicable.

Applied to files:

  • bin/gtr
🔇 Additional comments (4)
bin/gtr (4)

814-841: LGTM!

Flag parsing is well-structured, consistent with other commands in the file, and correctly handles unknown flags.


923-936: Good safeguards for uncommitted changes and untracked files.

The checks for uncommitted changes (staged and unstaged) and untracked files properly protect against accidental data loss.


894-896: Good: Fetch with proper error handling.

The git fetch origin --prune with || log_warn properly guards this non-critical command per the coding guidelines, ensuring the script continues even if fetch fails.


1356-1360: LGTM!

Help text is clear, accurately describes the new flags, and properly notes the gh CLI dependency.

Comment on lines +888 to +892
# Ensure gh CLI is available
if ! command -v gh >/dev/null 2>&1; then
log_error "GitHub CLI (gh) not found. Install from: https://cli.github.com/"
exit 1
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for gh repo view or gh auth in the entire bin/gtr file
rg -n 'gh (repo view|auth)' bin/gtr

Repository: coderabbitai/git-worktree-runner

Length of output: 58


🏁 Script executed:

# Also examine the merged-mode block context (lines 883-972)
sed -n '883,972p' bin/gtr | cat -n

Repository: coderabbitai/git-worktree-runner

Length of output: 3689


🏁 Script executed:

# Check if there's any gh initialization or auth check earlier in the file
rg -n 'gh ' bin/gtr | head -20

Repository: coderabbitai/git-worktree-runner

Length of output: 400


Add gh authentication/repository check before using gh pr list.

The code only verifies that gh CLI is installed (line 889) but doesn't check authentication or that this is a GitHub repository. When gh pr list fails on line 940 (due to auth issues or network errors), the error is silently suppressed with || true, causing the code to report "no merged PR found" (line 979) instead of the actual root cause.

Add after line 892:

if ! gh repo view >/dev/null 2>&1; then
  log_error "Not authenticated with GitHub or not a GitHub repository (run: gh auth login)"
  exit 1
fi

This ensures early failure with clear diagnostics rather than misleading skip messages.

🤖 Prompt for AI Agents
In bin/gtr around lines 888 to 892, after verifying gh is installed, add a check
that gh can access the repository and the user is authenticated by invoking gh
repo view and, on failure, call log_error with a message like "Not authenticated
with GitHub or not a GitHub repository (run: gh auth login)" and exit 1; this
prevents later silent failures from commands like gh pr list and surfaces
authentication or repo issues early.

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