with one click
cleanup-worktrees
// Safely clean up git worktrees by verifying code has been merged to main
// Safely clean up git worktrees by verifying code has been merged to main
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | cleanup-worktrees |
| description | Safely clean up git worktrees by verifying code has been merged to main |
| user-invocable | true |
| allowed-tools | Bash, Read, Grep, Glob, AskUserQuestion, mcp__atlassian__jira_search, mcp__atlassian__jira_get_issue, mcp__atlassian__jira_get_transitions, mcp__atlassian__jira_transition_issue, mcp__linear__get_issue |
CRITICAL: Every invocation of this command MUST discard all prior analysis results, cached data, and previous conclusions ā and start from scratch. Re-analyze every worktree fresh, regardless of any earlier runs.
Before any analysis, you MUST:
git fetch --prune origin to get the latest remote state and remove stale tracking branchesBegin output with: "Fresh analysis started ā all prior results discarded."
Safely clean up git worktrees by thoroughly verifying code has been merged to main.
CRITICAL: When in doubt, DO NOT DELETE. It's better to leave a worktree that could be deleted than to lose work.
cd ~/$REPO_NAME
git fetch --prune origin
git worktree list
cd <worktree-path>
git status --porcelain
git branch --show-current
git log --oneline -5
cd <worktree-path>
git status --porcelain
git stash list
cd <worktree-path>
git log origin/main..HEAD --oneline
cd <worktree-path>
branch=$(git branch --show-current 2>/dev/null || echo "DETACHED")
echo "Branch: $branch"
This is critical - Don't just check if a PR with the same name was merged. Verify the actual changes exist in main.
cd <worktree-path>
# Get files changed vs main
git diff --name-only origin/main...HEAD
# Pick key functions/code blocks added in the worktree
# Search for them in main
cd ~/${REPO_NAME}
git log --oneline --all -S "<unique-code-snippet>" -- <file-path>
grep -r "<unique-identifier>" <relevant-paths>
cd <worktree-path>
branch=$(git branch --show-current)
if [ -n "$branch" ]; then
gh pr list --head "$branch" --state all --json number,state,mergedAt,title,url
# Also check if branch was merged via different PR name
gh pr list --state merged --search "head:$branch" --json number,state,mergedAt,title
fi
# Branch names follow pattern: PROJ-XXX-description
# Folder names follow pattern: ${REPO_NAME}-PROJ-XXX
branch=$(git branch --show-current)
task_id=$(echo "$branch" | grep -oE '[A-Z]+-[0-9]+')
# Or from folder name
folder_name=$(basename "$(pwd)")
task_id=$(echo "$folder_name" | grep -oE '[A-Z]+-[0-9]+')
Use the configured ticket provider's MCP tool (e.g., mcp__atlassian__jira_get_issue for Jira, mcp__linear__get_issue for Linear) with the task ID to check:
If worktree has no branch or is in detached HEAD state:
cd <worktree-path>
git log --oneline | head -10
ls -la <worktree-path>
find <worktree-path> -type f -name "*.ts" -o -name "*.tsx" -mtime -7 | head -20
cd <worktree-path>
git diff --stat HEAD~5..HEAD 2>/dev/null || git diff --stat
Create a detailed table for each worktree:
| Worktree | Branch | Ticket Status | Uncommitted | Unpushed | PR Status | Code in Main | Safe |
|---|---|---|---|---|---|---|---|
| path | name | status | Yes/No | Yes/No | state | Verified/No | ? |
SAFE TO DELETE (ALL must be true):
git status --porcelain empty)git stash list empty)NOT SAFE / UNCERTAIN (ANY of these = DO NOT DELETE):
Show clearly:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
WORKTREE CLEANUP ANALYSIS
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
SAFE TO DELETE (code verified in main):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. /path/to/worktree-1
Branch: feature-xyz
Ticket: PROJ-123 (Done)
PR: #456 (Merged on 2024-01-15)
Verification: Key changes found in main at commit abc123
NOT SAFE (do not delete):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. /path/to/worktree-2
Branch: feature-abc
Reason: Has 3 uncommitted files
2. /path/to/worktree-3
Branch: (none - detached HEAD)
Reason: Ticket PROJ-456 is "In Progress"
UNCERTAIN (keeping to be safe):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. /path/to/worktree-4
Branch: fix/something
Reason: Could not verify code in main, keeping as precaution
Do you want to proceed with deleting the SAFE worktrees?
[List exact worktrees that will be deleted]
Type "yes" to confirm, or specify which ones to skip.
cd ~/${REPO_NAME}
# For each confirmed worktree:
git worktree remove <path> --force
# Only delete branch if it exists and PR was merged
git branch -D <branch-name>
# Delete remote branch if exists
git push origin --delete <branch-name> 2>/dev/null || true
For each deleted worktree where the PR was merged, transition the ticket task to "In Testing":
# Branch names follow pattern: PROJ-XXX-description
task_id=$(echo "$branch_name" | grep -oE '[A-Z]+-[0-9]+')
Use the configured ticket provider's transition tool (e.g., mcp__atlassian__jira_get_transitions for Jira) with the task ID to get available transitions.
Look for a transition with name containing "In Testing", "Testing", or "QA".
Use the configured ticket provider's transition tool (e.g., mcp__atlassian__jira_transition_issue for Jira) with:
transition_id: The ID of the "In Testing" transitioncomment: "Code merged to main and deployed. Ready for testing."āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
TICKET TASK TRANSITIONS
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
TRANSITIONED TO "IN TESTING":
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. PROJ-123 - Feature description
Previous status: In Review
New status: In Testing
PR: #456 (Merged)
SKIPPED (no transition needed):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. PROJ-456 - Already in Testing/Done
2. PROJ-789 - No matching transition available
FAILED:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. PROJ-999 - Transition not allowed from current status
Rules for transitioning:
cd ~/${REPO_NAME}
git worktree prune
git remote prune origin
git worktree list # Show final state
After cleaning up worktrees, also clean up stale remote branches.
cd ~/${REPO_NAME}
git fetch --prune origin
git branch -r | grep -v HEAD | sort
gh pr list --state all --json headRefName,state,mergedAt,number,title --limit 200 | jq -r '.[] | "\(.headRefName)|\(.state)|\(.mergedAt // "null")|\(.number)|\(.title)"' | sort
SAFE TO DELETE (merged):
MERGEDgit log origin/main)SAFE TO DELETE (closed without merge):
CLOSED (not merged)NOT SAFE (keep):
OPEN - active workmain, qa, uat, prodUNCERTAIN (keep to be safe):
Show clearly:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
REMOTE BRANCH CLEANUP ANALYSIS
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
SAFE TO DELETE (PRs merged, code verified in main):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. origin/PROJ-123-feature-name
PR: #456 (MERGED 2024-01-15) - "feat: add feature"
2. origin/fix/some-bug
PR: #789 (MERGED 2024-01-10) - "fix: resolve bug"
CLOSED (no merge, safe to delete):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. origin/test-branch
PR: #100 (CLOSED without merge)
2. origin/backup-PROJ-123
No PR found - backup branch
NOT SAFE (active work or open PRs):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. origin/PROJ-456-active-work
PR: #999 (OPEN) - active development
2. origin/dependabot/npm_and_yarn/package-1.0.0
Open PR from Dependabot
UNCERTAIN (keeping to be safe):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. origin/old-feature-branch
PR: #50 (CLOSED) - may have useful code
SUMMARY:
- Safe to delete (merged): X branches
- Safe to delete (closed): Y branches
- NOT safe (active): Z branches
- Uncertain (keeping): W branches
- Protected: 4 branches (main, qa, uat, prod)
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Use AskUserQuestion tool with options:
Delete in batches of 10 to avoid timeouts:
cd ~/${REPO_NAME}
git push origin --delete branch1 branch2 branch3 branch4 branch5 branch6 branch7 branch8 branch9 branch10
Important:
cd ~/${REPO_NAME}
git remote prune origin
echo "=== Remaining Remote Branches ===" && git branch -r | grep -v HEAD | wc -l && echo "branches remaining"
git branch -r | grep -v HEAD | sort
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
CLEANUP COMPLETE
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
WORKTREES DELETED: X
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
worktree-1 (reason)
ā
worktree-2 (reason)
REMOTE BRANCHES DELETED: Y
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
⢠Z branches with merged PRs
⢠W branches with closed PRs / backups
REMAINING WORKTREES: A
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. main (primary worktree)
2. active-worktree (reason kept)
REMAINING REMOTE BRANCHES: B
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
⢠C active development branches
⢠D dependabot PRs (open)
⢠4 protected branches (main, qa, uat, prod)
⢠E uncertain (kept for safety)
NOTE: If git stashes exist, run `git stash list` to review
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# Worktree added a new function `calculateMetrics` in src/utils/metrics.ts
# Verify it exists in main:
cd ~/${REPO_NAME}
grep -r "calculateMetrics" src/utils/metrics.ts
# If found ā code is in main
# Just checking PR was merged by title ā NOT ENOUGH
gh pr list --state merged --search "PROJ-123"
# This doesn't prove the code is actually there!