# Mode 3: Worktree Cleanup ## Overview This mode safely removes git worktrees after work is complete. It includes safety checks for uncommitted changes, optional branch deletion, and cleanup verification. ## When to Use - User says "remove worktree [name]" - User wants to "clean up worktrees" - User mentions "delete" or "cleanup" with "worktree" - After feature is merged and branch is no longer needed ## Workflow ### Phase 0: Prerequisites & Context #### Step 0.1: Verify Git Repository ```bash git rev-parse --is-inside-work-tree ``` **Expected Output:** ``` true ``` **If fails:** - Stop immediately - Error: "Not in a git repository" #### Step 0.2: List Current Worktrees ```bash git worktree list ``` **Purpose:** - Show user all available worktrees - Help identify what to remove - Detect if worktrees exist **Expected Output:** ``` /Users/connor/myapp abc123 [main] /Users/connor/myapp-feature-a def456 [feature-a] /Users/connor/myapp-bugfix-123 ghi789 [bugfix-123] ``` **If only one worktree (main):** - Message: "No additional worktrees to remove" - Stop (nothing to cleanup) --- ### Phase 1: Identify Target Worktrees #### Step 1.1: Determine Cleanup Scope **Parse user request:** - "remove worktree **feature-a**" → Single worktree - "remove **all** worktrees" → All non-main worktrees - "clean up worktrees" → All or let user select? - "remove worktree at **/path**" → By path **Extract:** - `CLEANUP_MODE`: "single" | "all" | "selective" - `TARGET`: branch name or path #### Step 1.2: Map to Worktree Paths **For single worktree:** ```bash # If user provided branch name TARGET_BRANCH="feature-a" TARGET_PATH=$(git worktree list | grep "\[$TARGET_BRANCH\]" | awk '{print $1}') # If user provided path TARGET_PATH="/Users/connor/myapp-feature-a" TARGET_BRANCH=$(git worktree list | grep "$TARGET_PATH" | grep -oP '\[\K[^\]]+') ``` **Verify target exists:** ```bash if [ -z "$TARGET_PATH" ]; then echo "Error: Worktree not found" exit 1 fi ``` **For all worktrees:** ```bash # Get all non-main worktrees MAIN_PATH=$(git rev-parse --show-toplevel) mapfile -t ALL_WORKTREES < <(git worktree list | grep -v "$MAIN_PATH" | awk '{print $1}') ``` #### Step 1.3: Show Cleanup Plan **For single:** ``` Planning to remove: Branch: feature-a Path: /Users/connor/myapp-feature-a Status: [to be determined in next step] ``` **For all:** ``` Planning to remove all worktrees: feature-a → /Users/connor/myapp-feature-a bugfix-123 → /Users/connor/myapp-bugfix-123 Main worktree will be preserved: main → /Users/connor/myapp ``` **Confirm:** "Proceed with cleanup? (yes/no)" --- ### Phase 2: Safety Checks #### Step 2.1: Check for Uncommitted Changes **For each target worktree:** ```bash cd "$WORKTREE_PATH" CHANGES=$(git status --porcelain) if [ -n "$CHANGES" ]; then echo "⚠️ Uncommitted changes detected" git status --short fi ``` **If changes found:** ``` ⚠️ Warning: feature-a has uncommitted changes: M src/auth.ts ?? src/new-file.ts Options: 1. Abort cleanup (save your work first) 2. Show diff and decide 3. Continue anyway (DANGER: changes will be lost) 4. Stash changes automatically ``` **Recommended:** Abort and let user save work **If user chooses stash:** ```bash cd "$WORKTREE_PATH" git stash push -m "Auto-stash before worktree removal $(date)" STASH_CREATED=true ``` **Record stash location:** ``` ✓ Changes stashed in main repository To recover: git stash list Stash name: stash@{0}: Auto-stash before worktree removal... ``` #### Step 2.2: Check Branch Merge Status **Determine if branch is merged:** ```bash BRANCH_NAME=$(cd "$WORKTREE_PATH" && git branch --show-current) BASE_BRANCH="main" # or detect from git config # Check if merged to base if git branch --merged "$BASE_BRANCH" | grep -q "$BRANCH_NAME"; then MERGE_STATUS="merged" else MERGE_STATUS="unmerged" fi ``` **Report status:** ``` Branch status: ✓ feature-a: Merged to main ⚠️ bugfix-123: NOT merged to main ``` **If unmerged:** ``` ⚠️ Warning: bugfix-123 is not merged to main Unmerged commits: [Show last few commits unique to this branch] Options: 1. Abort cleanup (merge first) 2. Create backup branch (bugfix-123-backup) 3. Continue and delete branch 4. Remove worktree but keep branch ``` #### Step 2.3: Check Active Processes **Check if worktree is in use:** ```bash # Check for running processes in worktree lsof +D "$WORKTREE_PATH" 2>/dev/null # Check for open editors if lsof +D "$WORKTREE_PATH" | grep -q "vim\|nvim\|code\|claude"; then echo "⚠️ Active processes detected in worktree" fi ``` **If processes found:** ``` ⚠️ Warning: Processes are using this worktree: - VS Code (PID: 12345) - Claude Code (PID: 67890) Please close these applications first, or force cleanup (not recommended). ``` --- ### Phase 3: Execute Cleanup #### Step 3.1: Remove Worktree **Standard removal:** ```bash git worktree remove "$WORKTREE_PATH" ``` **If removal fails (locked):** ```bash # Try with force git worktree remove --force "$WORKTREE_PATH" ``` **Expected Output:** ``` ✓ Removed worktree: /Users/connor/myapp-feature-a ``` **Verify removal:** ```bash if ! git worktree list | grep -q "$WORKTREE_PATH"; then echo "✓ Worktree removed from git" fi if [ ! -d "$WORKTREE_PATH" ]; then echo "✓ Directory removed" fi ``` #### Step 3.2: Handle Branch Deletion **Ask user:** ``` Worktree removed successfully. Delete branch 'feature-a' too? (yes/no/backup) Options: yes - Delete branch permanently no - Keep branch (can checkout later) backup - Create backup branch before deleting ``` **If yes:** ```bash git branch -d "$BRANCH_NAME" ``` **If force needed (unmerged):** ```bash # Warn user first echo "⚠️ Branch is unmerged. Use -D to force delete." read -p "Force delete? (yes/no): " force if [ "$force" == "yes" ]; then git branch -D "$BRANCH_NAME" fi ``` **If backup:** ```bash # Create backup branch BACKUP_NAME="${BRANCH_NAME}-backup-$(date +%Y%m%d)" git branch "$BACKUP_NAME" "$BRANCH_NAME" echo "✓ Created backup: $BACKUP_NAME" # Then delete original git branch -d "$BRANCH_NAME" ``` #### Step 3.3: Cleanup Verification **Verify complete removal:** ```bash # Check worktree list if git worktree list | grep -q "$WORKTREE_PATH"; then echo "✗ Worktree still in git worktree list" ERROR=true fi # Check directory if [ -d "$WORKTREE_PATH" ]; then echo "✗ Directory still exists" ERROR=true fi # Check branch (if deleted) if [ "$DELETE_BRANCH" == "yes" ]; then if git branch | grep -q "$BRANCH_NAME"; then echo "✗ Branch still exists" ERROR=true fi fi ``` **Success confirmation:** ``` ✓ Cleanup verified: ✓ Worktree removed from git ✓ Directory deleted ✓ Branch deleted (if requested) ``` --- ### Phase 4: Batch Cleanup (If Multiple Worktrees) **For cleanup mode "all":** #### Step 4.1: Process Each Worktree ```bash REMOVED=() FAILED=() KEPT_BRANCHES=() DELETED_BRANCHES=() for worktree in "${ALL_WORKTREES[@]}"; do echo "Processing: $worktree" # Get branch name BRANCH=$(git worktree list | grep "$worktree" | grep -oP '\[\K[^\]]+') # Safety checks cd "$worktree" if [ -n "$(git status --porcelain)" ]; then echo " ⚠️ Uncommitted changes - skipping" FAILED+=("$worktree (uncommitted changes)") continue fi # Remove worktree if git worktree remove "$worktree"; then REMOVED+=("$worktree") echo " ✓ Removed" # Ask about branch (or use default policy) if git branch --merged main | grep -q "$BRANCH"; then git branch -d "$BRANCH" DELETED_BRANCHES+=("$BRANCH") echo " ✓ Deleted merged branch: $BRANCH" else KEPT_BRANCHES+=("$BRANCH") echo " ℹ️ Kept unmerged branch: $BRANCH" fi else FAILED+=("$worktree") echo " ✗ Failed" fi done ``` #### Step 4.2: Batch Cleanup Summary ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Batch Cleanup Complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Removed: ${#REMOVED[@]} worktrees Failed: ${#FAILED[@]} worktrees Successfully removed: ✓ /Users/connor/myapp-feature-a ✓ /Users/connor/myapp-feature-b Failed to remove: ✗ /Users/connor/myapp-bugfix-123 (uncommitted changes) Branches deleted: ✓ feature-a (merged) ✓ feature-b (merged) Branches kept: ℹ️ bugfix-123 (unmerged) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` --- ### Phase 5: Post-Cleanup Actions #### Step 5.1: Show Remaining Worktrees ```bash git worktree list ``` **Output:** ``` Remaining worktrees: /Users/connor/myapp (main) ← current /Users/connor/myapp-bugfix-123 (bugfix-123) ← has uncommitted changes ``` #### Step 5.2: Cleanup Orphaned References **Prune removed worktrees:** ```bash git worktree prune ``` **Purpose:** - Remove administrative files for deleted worktrees - Clean up .git/worktrees directory **Output:** ``` ✓ Pruned orphaned worktree references ``` #### Step 5.3: Show Recovery Information **If changes were stashed:** ``` 📦 Stashed Changes: Your uncommitted changes are saved in git stash: Stash: stash@{0} Message: Auto-stash before worktree removal 2025-09-04 To recover: git stash list # List all stashes git stash show stash@{0} # Preview changes git stash pop stash@{0} # Apply and remove stash git stash apply stash@{0} # Apply but keep stash ``` **If branches were backed up:** ``` 💾 Backed Up Branches: Created backup branches before deletion: feature-a-backup-20250904 feature-b-backup-20250904 To restore: git checkout feature-a-backup-20250904 git branch feature-a # Recreate original branch To remove backups: git branch -D feature-a-backup-20250904 ``` #### Step 5.4: Disk Space Reclaimed **Calculate space saved:** ```bash # Estimate space freed (approximate) echo "Disk space reclaimed: ~$(du -sh $WORKTREE_PATH 2>/dev/null || echo 'N/A')" ``` **Show before/after:** ``` Disk Space Summary: Before: 2.4 GB (3 worktrees) After: 800 MB (1 worktree) Reclaimed: ~1.6 GB ``` --- ## Safety Protocols ### Before Removal **Mandatory checks:** - [ ] Worktree exists in git worktree list - [ ] Not removing main/current worktree - [ ] Checked for uncommitted changes - [ ] Checked for unmerged commits - [ ] Confirmed with user ### During Removal **Safe removal process:** - [ ] Use `git worktree remove` (not rm -rf) - [ ] Verify removal succeeded - [ ] Handle locked worktrees appropriately - [ ] Preserve stashes if needed ### After Removal **Verification:** - [ ] Worktree not in git worktree list - [ ] Directory removed - [ ] Branch handled per user request - [ ] Stashes accessible (if created) - [ ] Backups created (if requested) --- ## Error Handling ### Error: Uncommitted Changes ``` Error: Cannot remove worktree with uncommitted changes Current changes in feature-a: M src/auth.ts ?? src/new-file.ts Solutions: 1. Commit changes: cd /path/to/worktree git add . git commit -m "Save work" 2. Stash changes: Let me stash them automatically 3. Force remove (DANGER): Changes will be lost forever ``` ### Error: Worktree Locked ``` Error: Worktree is locked Reason: Usually means processes are using it Solutions: 1. Close all applications using this worktree 2. Check: lsof +D /path/to/worktree 3. Force remove: git worktree remove --force ``` ### Error: Branch Checkout Elsewhere ``` Error: Cannot delete branch - checked out elsewhere Branch 'feature-a' is checked out at: /other/path/to/worktree Solution: 1. Remove that worktree first 2. Or skip branch deletion (keep branch) ``` ### Error: Unmerged Commits ``` Error: Branch has unmerged commits Commits not in main: abc123 feat: Add new feature def456 fix: Bug fix Options: 1. Merge first: git merge feature-a 2. Create backup: feature-a-backup 3. Force delete: git branch -D feature-a ``` --- ## Advanced Features ### Feature 1: Smart Cleanup **Auto-detect removable worktrees:** ```bash # Find merged branches git for-each-ref --format='%(refname:short)' refs/heads/ | while read branch; do if git branch --merged main | grep -q "$branch" && [ "$branch" != "main" ]; then echo "Removable: $branch (merged)" fi done ``` **Offer cleanup:** ``` Found 3 worktrees with merged branches: feature-a (merged 3 days ago) feature-b (merged 1 week ago) bugfix-old (merged 2 weeks ago) Clean up all merged worktrees? (yes/select/no) ``` ### Feature 2: Archive Instead of Delete **Create archive before removal:** ```bash ARCHIVE_DIR="$HOME/.git-worktree-archives" ARCHIVE_NAME="${BRANCH_NAME}-$(date +%Y%m%d-%H%M%S).tar.gz" # Archive worktree tar -czf "$ARCHIVE_DIR/$ARCHIVE_NAME" -C "$(dirname $WORKTREE_PATH)" "$(basename $WORKTREE_PATH)" echo "✓ Archived to: $ARCHIVE_DIR/$ARCHIVE_NAME" echo " To restore: tar -xzf $ARCHIVE_NAME" ``` ### Feature 3: Cleanup Report **Generate detailed report:** ```markdown # Worktree Cleanup Report Date: 2025-09-04 15:30:00 ## Removed Worktrees - feature-a (merged, 15 commits, 3 days old) - feature-b (merged, 8 commits, 1 week old) ## Branches Deleted - feature-a (merged to main) - feature-b (merged to main) ## Disk Space Reclaimed - Total: 1.6 GB - Per worktree: ~800 MB ## Stashes Created - stash@{0}: Auto-stash feature-a - stash@{1}: Auto-stash feature-b ## Recommendations - Review stashes and apply if needed - Consider cleanup policy for future - Monitor disk usage regularly ``` --- ## Stop Conditions **Stop immediately if:** - [ ] Not in a git repository - [ ] Target worktree doesn't exist - [ ] User cancels during confirmation - [ ] Trying to remove main worktree - [ ] Uncommitted changes and user aborts **Continue with warnings if:** - [ ] Some worktrees fail in batch mode - [ ] Branch deletion fails - [ ] Prune operation fails --- ## Success Criteria - [ ] Worktree removed from git worktree list - [ ] Directory deleted from filesystem - [ ] Branch handled per user request (deleted/kept/backed up) - [ ] No uncommitted changes lost - [ ] Cleanup verified - [ ] Recovery information provided (if applicable) --- ## Example: Complete Flow ``` User: Remove the feature-authentication worktree Claude: Checking worktree: feature-authentication Location: /Users/connor/myapp-feature-authentication Safety checks: ✓ No uncommitted changes ✓ Branch merged to main ✓ No active processes Proceed with removal? (yes/no) User: yes Claude: Removing worktree... ✓ Worktree removed from git ✓ Directory deleted Delete branch 'feature-authentication' too? (yes/no/backup) User: yes Claude: ✓ Branch deleted (was merged to main) Cleanup complete! Remaining worktrees: /Users/connor/myapp (main) ← current Disk space reclaimed: ~800 MB ```