with one click
safe-git-worktree-branch-cleanup
// Safely update local main and clean stale local git branches/worktrees without deleting dirty or still-attached work.
// Safely update local main and clean stale local git branches/worktrees without deleting dirty or still-attached work.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | safe-git-worktree-branch-cleanup |
| description | Safely update local main and clean stale local git branches/worktrees without deleting dirty or still-attached work. |
| version | 1.0.1 |
| author | Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["git","worktree","branch","cleanup","maintenance","safety"]}} |
Use when the user asks to:
mainThis workflow is for repositories that may have many old worktrees, detached review trees, and branches whose upstream refs were pruned.
Related skill:
branch-squash-validity-and-stale-cleanup — use when the user explicitly wants each local branch judged by a synthetic squash of current local state versus latest origin/main, with a disposable rebase-onto-latest-main test before deciding whether to preserve or delete it.main) to match the latest remote HEAD when the root worktree is cleanUser-specific requirement:
main-based repos, align local main with the latest origin/main via fetch + fast-forward when safegit pull --ff-only; first check whether the incoming remote diff touches those same paths, and if not, fast-forward is usually still safeorigin/main unless the user explicitly asks for aggressive cleanup.Run:
pwd
git rev-parse --show-toplevel
If not a repo, discover likely repositories or worktrees under the workspace using repository-aware search tools before proceeding.
Run:
git branch --show-current
git status --short --branch
git remote -v
git worktree list --porcelain
git for-each-ref --format='%(refname:short)|%(upstream:short)|%(committerdate:iso8601)|%(subject)' refs/heads
This reveals:
Run:
git fetch origin --prune
Then check:
git rev-parse main
git rev-parse origin/main
git branch --merged origin/main
Also identify branches whose upstream ref was removed:
for b in $(git for-each-ref --format='%(refname:short)' refs/heads); do
u=$(git for-each-ref --format='%(upstream:short)' refs/heads/$b)
if [ -n "$u" ] && ! git show-ref --verify --quiet refs/remotes/$u; then
echo "$b|$u"
fi
done
For each worktree, collect:
Only treat a worktree as immediately removable when all are true:
Do not remove:
In repositories that use many temporary worktrees for PR follow-up, a branch-attached worktree is often still stale even though it is not detached.
Before preserving branch-backed worktrees, query actual open PRs and compare them to local branch/worktree state.
If GitHub CLI is available:
gh pr list --state open --json number,title,headRefName,isDraft,url
Then classify each branch-backed worktree using this stricter order:
Strong stale signals for branch-backed worktrees:
git fetch --prunegh pr list --state all --head <branch> shows the PR was already merged/closedorigin/mainCounter-signal that should usually preserve a branch-backed worktree even with no PR:
But do not over-preserve branch-backed worktrees just because they are not perfectly clean. Some branch worktrees are effectively stale except for disposable helper files. Check for repo-local temp artifacts such as:
.tmp-issue-*.md.tmp-pr-body*.mdIf those temp files are the only dirt in an otherwise stale merged/non-open-PR worktree, treat the worktree as effectively clean stale residue and remove it after confirming no tracked source changes remain.
Additional practical check for issue/PR body scratch files:
ISSUE_*.md, PR_BODY*.md, or similargh issue view <n> --json body or gh pr view <n> --json bodySpecial case: open PRs can still have removable helper worktrees.
pr157-mainrewrite, pr158-mainrewrite, pr157-rebase, etc.HEAD exactly matches the open PR's remote head commit, it is just a redundant local clone of the PR state and is safe to remove.Additional practical stale signals for branch-backed worktrees:
MERGED and the upstream ref is [gone].tmp-pr-body.mdIn that case, it is usually safe to:
Important practical lessons:
For each local branch, determine:
origin/main or notorigin/mainA local branch is safe to delete only when all are true:
mainorigin/main, or confirmed as stale residue from an already merged/closed PR that no longer needs local preservationNotes:
git cherry origin/main <branch> is useful when a squash/rebase merge means the branch is not a direct ancestor of origin/main even though its PR is already merged.Some worktrees look dirty only because of transient helper files created during agent workflows.
Important practical case from repo-local workspace cleanup:
origin/mainTypical signal pattern:
CLOSED or MERGED, or no open PR existsgit rev-list --left-right --count origin/main...<branch> shows the branch is far from latest mainorigin/main quickly produces broad structural conflictsgit status --short inside the worktree shows a small focused set of dirty filesgit diff --stat for those dirty files shows coherent implementation work rather than conflict junk or temp-file residueRecommended decision flow for this case:
git -C <worktree> status --short --branch
git -C <worktree> diff --stat
git -C <worktree> diff -- <paths...>
git -C <worktree> diff --stat origin/main -- <paths...>
git rebase origin/main in a disposable helper worktree to judge whether the branch history itself is salvageable or just stalePractical interpretation:
Additional practical case: branch head can be effectively stale or even net-empty, while the worktree still holds meaningful unpublished implementation
Typical signal pattern:
origin/main directly or have git rev-list origin/main...<branch> close to 0 0, behind 1, or another misleadingly small valuegit status --short in the attached worktree shows real tracked edits plus meaningful untracked source/test files.tmp-issue-*.md, but actual implementation files under src/ or tests/Example summary language:
stale branch pointer + meaningful local worktree patchHandling rule:
Additional practical case: branch head can be effectively stale or even net-empty, while the worktree still holds meaningful unpublished implementation
Typical signal pattern:
origin/main directly or have git rev-list origin/main...<branch> close to 0 0, behind 1, or another misleadingly small valuegit status --short in the attached worktree shows real tracked edits plus meaningful untracked source/test files.tmp-issue-*.md, but actual implementation files under src/ or tests/Example summary language:
stale branch pointer + meaningful local worktree patchHandling rule:
User-specific validation heuristic for repo-local cleanup:
main/origin/main tree before deletingorigin/main is a fast way to tell whether the branch history itself is stale; broad immediate conflicts are evidence that the branch lineage is stale even if a small current dirty patch is still worth preservingUseful summary language:
stale branch history + meaningful local dirty workfully stale residue and actively healthy branchCheck for obviously disposable artifacts such as:
.tmp-pr-body.md.hermes/ when they are clearly local tool state rather than repo sourcePractical rule:
git status --porcelainD postcss.config.mjs), preserve the worktreeBe especially careful with .hermes/-style directories:
.hermes/ is the only remaining dirt and the associated remote branch/PR is already merged, treat the worktree as stale and remove it with git worktree remove --force ... if neededDo not delete dirty worktrees automatically if the remaining changes are real project files.
git statusA detached worktree can look clean in a simple git status --short --branch snapshot, yet git worktree remove <path> may still refuse with:
contains modified or untracked files, use --force to delete it
When this happens, do not immediately force-remove blindly. First inspect inside the worktree:
git -C <path> status --short --branch
git -C <path> diff --stat || true
git -C <path> ls-files --others --exclude-standard || true
Interpretation:
diff --stat is empty and ls-files --others is empty, the refusal is just conservative Git behavior and git worktree remove --force <path> is acceptableThis case showed up with detached PR follow-up worktrees that were effectively clean but still required --force to remove.
In PR-heavy repos, the primary/root worktree on main can accumulate tracked-file edits that are not true main work. They may be accidental spillover from an open-PR worktree or another local worktree.
Before deciding that root tracked-file changes must be preserved, compare them against relevant kept worktrees:
git diff --name-only
For each dirty tracked file, compare the root file against the corresponding file in likely source worktrees, especially open-PR worktrees:
git diff --no-index -- path/in/root path/in/other/worktree || true
Interpretation:
This check is especially important before restoring dirty tracked files in main so that main can be fast-forwarded to origin/main safely.
In repositories that check in agent skills or guidance under paths like .agents/skills/** or docs/**, the root main worktree can become dirty because of local exploratory edits to a skill or guidance file while you were debugging or documenting a different task.
Important split:
main can fast-forward cleanlymain dirty indefinitelySafe handling:
git diff -- .agents/skills/... docs/...
git restore --worktree -- <path>
Practical rule:
main fast-forward during workspace cleanupImportant follow-up case from corp-web-japan cleanup:
main worktree ends the cleanup with exactly one meaningful repo-local skill/doc edit left (for example a checked-in .agents/skills/**/SKILL.md improvement) after all stale worktrees/branches were removedmain dirty indefinitely if the user actually wants that edit preservedorigin/mainmain worktree and fast-forward it to origin/mainmain and not to open a PR yet:
origin/mainbackup/...mainmain to origin/maingit stash when the user values inspectable branch/worktree preservation more than stash-based safekeepingUseful summary labels:
root-local skill residue -> separate docs PRroot-local meaningful skill edit -> local backup branch before main refreshAdditional lesson from repeated repo-local cleanup:
.agents/skills/**, restore those files first and then re-evaluate the worktree for removalNot every clean detached worktree is disposable. A detached worktree can point at a commit that is not contained in any local branch or remote branch. Deleting it blindly can lose the only remaining reference to that local commit.
Check this before removing a clean detached worktree whose purpose is unclear:
head=$(git -C <path> rev-parse HEAD)
git branch --contains "$head"
git tag --contains "$head"
Interpretation:
Practical rule:
HEAD is an orphan local commit are not stale by defaultSometimes the user wants the workspace itself cleaned up even when several detached worktrees are still worth preserving as local history. In that case, the right move is often not to keep many detached worktrees around. Instead:
dirty detached keep as worktreeclean detached keep as commitclean detached stale/removeclean detached keep as commit, create a named local backup branch first:git branch backup/<descriptive-name> <detached-sha>
Examples:
backup/pr103-image-click-closebackup/pr223-numeric-idsbackup/pr240-typography-refreshgit worktree remove --force <path>
Why this is valuable:
Practical rule:
promote to backup branch + remove worktree over leaving the detached worktree in placeSome repositories accumulate many detached helper worktrees for one investigation or PR follow-up, for example:
pr103-zoomable-figurepr103-value-diagram-webppr103-value-diagram-modalpr103-image-click-closeThese may all be orphan local commits, yet still be safely reducible if they form a strict ancestry chain.
Check this with:
git merge-base --is-ancestor <older-sha> <newer-sha>
Interpretation:
A is an ancestor of B, keeping B preserves reachability to AA -> B -> C -> D, you do not need four detached worktrees to preserve that historyD) and remove the older detached helpers (A, B, C), because their commits remain reachable from DPractical rule:
A detached worktree may match the final remote head commit of an already merged PR. That detached worktree is usually just a leftover local clone of the merged PR state.
Check this with:
env -u GITHUB_TOKEN gh pr view <number> --json headRefOid,state
Then compare the detached HEAD to headRefOid.
Interpretation:
MERGED and the detached HEAD exactly equals headRefOid, the detached worktree is redundant and removablePractical rule:
detached HEAD == merged PR remote head as stale helper residue, not as a unique local preservation obligationA local branch/worktree may have no PR of its own yet still be redundant because it simply points to the same commit as another open-PR branch.
This often happens with helper names like pr240-rewrite-main, pr240-rebase-main, or other local-only convenience branches.
Related important case: backup/pr123-* branches often do not match the real historical PR head branch name at all.
For example, a local branch like backup/pr259-ci-fix may correspond to a merged PR whose actual GitHub head branch was something different like feat/whitepapers-13-14-local.
That means gh pr list --state all --head <local-branch> can incorrectly return nothing even though the branch is clearly PR residue.
In those cases, extract the PR number from the local branch name and inspect the PR directly:
env -u GITHUB_TOKEN gh pr view 259 --json number,state,title,url,headRefName,headRefOid,baseRefName,mergeCommit
Interpretation:
MERGED, the local backup/pr123-* branch is very likely post-merge residue or a local follow-up line derived from that merged PRorigin/main, has no attached meaningful dirty worktree, and disposable rebase checks conflict immediately, treat the branch history as stale even if the branch once represented meaningful local experimentationbackup/pr123-* variants exist, compare them against one another and keep only the one that still preserves unique value, if anyPractical rule:
backup/pr123-*, do not rely only on --head <branch-name> PR lookupCheck for aliasing before preserving a no-PR branch-backed worktree:
git rev-parse <local-helper-branch>
git rev-parse <open-pr-branch>
git rev-list --left-right --count <local-helper-branch>...<open-pr-branch>
Interpretation:
Practical rule:
no open PR for this branch name is not enough to preserve a helper branch/worktreeSome repositories accumulate local convenience branches and worktrees that are not tied to any open PR and no longer represent independent work. A common pattern is:
origin/main directlyHEAD is exactly equal to local main / origin/mainCheck this with:
git rev-parse <branch>
git rev-parse main
git rev-parse origin/main
git rev-list --left-right --count origin/main...<branch>
git -C <worktree> status --short --branch
Interpretation:
main and origin/main, and the worktree is clean, it is just a no-op alias of mainSafe handling:
git worktree remove <worktree>
git branch -D <branch>
Practical rule:
tracks origin/main + same SHA as main + clean worktree as a safe stale-deletion caseSometimes the repository ends up with both:
docs/publication-refactor-plan)pr287-rewrite)A nearby variant is a no-PR local helper branch that tracks origin/main but actually exists only as an alias for the current open PR head or for current main.
Examples from corp-web-japan cleanup included helper names like pr300-main, pr301-main, and fix/news-sitemap-indexing.
Signal patterns:
git rev-list --left-right --count origin/main...<branch> shows 0 0 or a tiny helper delta like 0 1git rev-parse <branch> exactly equals either:
origin/main tip, orheadRefOidInterpretation:
origin/main, it is just a redundant local alias of main and is staleheadRefOid but the branch name is not the official PR branch name, it is just a redundant local alias of the PR head and is stale once the official branch/worktree is synchronizedRecommended handling:
main or on the official PR branch namePractical rule:
origin/main or an open PR head under a convenience nameAnd the helper branch, not the official local branch, matches the current remote PR head commit.
Check this with:
env -u GITHUB_TOKEN gh pr view <number> --json headRefName,headRefOid
git rev-parse <official-local-branch>
git rev-parse <alias-local-branch>
git rev-parse origin/<official-local-branch>
Interpretation:
headRefOid, the alias is not independent work; it is just a local mirror of the current PR head under the wrong nameorigin/<official-local-branch>, the right cleanup is usually:
origin/<official-local-branch>Safe handling:
origin/<official-local-branch> when it has no local dirt:git -C <official-worktree> reset --hard origin/<official-local-branch>
git worktree remove <alias-worktree>
git branch -D <alias-branch>
Practical rule:
Some repositories accumulate local branch-attached helper worktrees with names like:
pr277-rewrite-latestpr280-rewrite-1114436pr281-rewrite-149d1e2These are not detached, and they may even track origin/main, but they can still be stale residue rather than meaningful active branches.
Typical signal pattern:
MERGED, or still/open only under a different canonical head branch namegit status --short --branch inside the attached worktree is cleangit rev-list --left-right --count origin/main...<branch> shows a small helper delta such as ahead 1, behind NRecommended handling:
env -u GITHUB_TOKEN gh pr view <number> --json number,state,headRefName,headRefOid,title,url
git -C <worktree> status --short --branch
git -C <worktree> diff --stat || true
Useful summary labels:
stale attached helper aliasmerged PR residue with clean attached worktreestale history + meaningful local dirty worktreeWhen a user asks whether a local branch is still valid or stale, do not rely only on raw commit count, branch age, or whether a PR once existed.
A better test is to evaluate the branch as if its current net effect were squashed into one commit, then see whether that single net patch still ports cleanly to the latest origin/main.
This is especially useful for:
backup/pr123-* branchesProcedure:
base=$(git merge-base origin/main <branch>)
tree=$(git rev-parse <branch>^{tree})
squash=$(printf 'TEMP SQUASH %s\n' <branch> | git commit-tree "$tree" -p "$base")
This does not modify the real branch history. It creates a temporary commit object representing the branch's net diff as one patch.
git diff --stat --find-renames origin/main...$squash --
git diff --name-status --find-renames origin/main...$squash --
git cherry origin/main $squash || true
tmp=$(mktemp -d)
wt="$tmp/rebase-check"
git worktree add --detach "$wt" "$squash"
git -C "$wt" rebase origin/main
Interpretation:
Useful summary labels:
valid branch: squash patch rebases cleanly onto latest mainstale branch: synthetic squash conflicts broadly on latest mainstale branch history + meaningful dirty detached patchAdditional practical case: judge the current worktree state, not only the committed branch tip
When the user explicitly asks whether a local branch is still valid given its current local changes, the right object to test is often not the branch tip tree alone.
A branch can be merged/stale in committed history while the attached worktree still contains meaningful uncommitted changes.
Conversely, a branch can look large or old, but its current worktree state may still squash and rebase cleanly onto latest origin/main.
Recommended procedure for branch-backed worktrees:
base=$(git merge-base origin/main <branch>)
<branch>^{tree} alone
Example pattern:
tmpidx=$(mktemp)
idxpath=$(git -C <worktree> rev-parse --git-path index)
cp "$idxpath" "$tmpidx"
GIT_INDEX_FILE="$tmpidx" git -C <worktree> add -A
worktree_tree=$(GIT_INDEX_FILE="$tmpidx" git -C <worktree> write-tree)
rm -f "$tmpidx"
squash=$(printf 'TEMP SQUASH %s\n' <label> | git commit-tree "$worktree_tree" -p "$base")
origin/main in a disposable detached worktreeInterpretation:
stale branch history + meaningful current local patchPractical lessons from corp-web-japan cleanup:
Practical lesson from corp-web-japan cleanup:
backup/pr223-rebase-squash can fail the squash-portability test, yet a detached worktree at the same HEAD may still contain a small meaningful local follow-up patch worth preservingOpen PR work often leaves multiple detached helper worktrees such as ...-pr285b, ...-pr285c, pr277-ci-fix, etc.
Do not preserve them just because the PR is open.
Check:
git rev-parse HEAD
env -u GITHUB_TOKEN gh pr view <number> --json headRefOid,state
Then, if needed, compare helper commits to one another:
git merge-base --is-ancestor <older-helper-sha> <newer-helper-sha>
Interpretation:
Practical lesson from corp-web-japan cleanup:
internal-events-demo-pr285c was a clean detached clone equal to open PR #285 head and safe to removeinternal-events-demo-pr285b was an older clean detached ancestor of that same line and also safe to removecta-glow-soften, pr301-lint, and similar detached follow-up clones showed the same pattern: once a clean official branch worktree was reset to the current remote PR head, the detached helper clones became redundant and removableorigin/<pr-branch>, and only after that remove the detached helper clonesIn fast-moving repos, the set of open PR head branches may change during the cleanup session itself. A branch that was non-open at the start can later become the real open PR head, and new local helper branches/worktrees can also appear mid-session.
Practical rule:
git fetch --prune, gh pr list --state open, and git worktree listAdditional practical case: a branch-attached no-op alias can point exactly at origin/main
Sometimes a local branch/worktree is clearly not an active PR branch, yet it still survives because it simply points at the same commit as origin/main or local main.
Examples are helper names like pr300-main, pr301-main, or other temporary labels created during rebase/review work.
Check this with:
git rev-parse <branch>
git rev-parse origin/main
git rev-list --left-right --count origin/main...<branch>
Interpretation:
origin/main and it has no open PR role, it is a no-op local alias and safe to deleteUseful summary label:
stale no-op alias: branch == origin/mainAdditional practical case: root staged residue copied from a merged detached helper worktree
Sometimes the root main worktree is not just dirty; it contains a staged set of files that exactly matches a clean detached helper worktree from a recently merged PR.
This can happen after ad hoc comparison, cherry-pick, or conflict-resolution experiments.
Safe handling:
git diff --cached --name-only
git diff --name-only
main work, drop them with targeted restore:git restore --staged --worktree -- <paths...>
Preferred rule:
This is safer than forcing a branch update that would overwrite a real local edit, and aligns better with users who find stash-based preservation low value compared with keeping an inspectable worktree/branch.
?? .worktrees/ noiseSome repositories keep linked worktrees under a repo-internal directory such as .worktrees/<name>.
After cleanup, the root worktree can still appear dirty with:
?? .worktrees/
This does not mean the remaining worktree is stale. It often just means the repo does not ignore its own local worktree container directory.
Safe handling:
git status clean.git/info/exclude such as:.worktrees/
Why this is useful:
.gitignore noise for machine-local worktree layoutIf you remove the worktree that your current shell session is effectively rooted in, subsequent shell startup or commands can fail with errors like:
getcwd: cannot access parent directories: No such file or directory
This is especially common when agent sessions were launched from a repo-internal .worktrees/... path.
Safe handling:
workdir=<repo-root> in tool calls or git -C <repo-root> ... command formsPractical rule:
Practical rule:
git fetch --prune, re-check open PR heads, and re-check git worktree list before each new deletion batch if the repo is actively changingorigin/main or PR heads can advance during the cleanup session itselfMERGED, while a different branch becomes the new active PR line; do not preserve the old branch/worktree based on stale earlier assumptionsgit branch -vv, git worktree list --porcelain, root git status --short --branch, and open PR query) and treat that last snapshot as the only authoritative statement of current statewhat I deleted during this session from what exists right now so the user can understand both the cleanup actions and any concurrent re-creationsmain worktree to the latest origin/main when the root checkout is clean so the workspace ends in a refreshed baselineAdditional practical case: branch names and PR head names can change mid-cleanup, so a stale candidate can become active before you delete it
In repositories with active stacked follow-up work, a branch that looked like a non-open-PR stale candidate at the start of the session can later become the actual head branch of a newly opened PR, or an official branch can be replaced by a rewrite/rebase alias that now carries the real PR head.
Check this immediately before any destructive deletion batch:
env -u GITHUB_TOKEN gh pr list --state open --json number,headRefName,headRefOid,title,url
git for-each-ref --format='%(refname:short)|%(objectname:short)' refs/heads
git worktree list --porcelain
Then verify for each deletion candidate:
headRefNameheadRefOidPractical interpretation:
non-open-PR at T1 does not guarantee non-open-PR at T2origin/main and has no open PR role, treat it as a no-op local alias and delete itIf main is not checked out in any worktree, update it directly:
git branch -f main origin/main
This is faster and safer than checking out main in a busy multi-worktree repo.
Verify:
git rev-parse main
git rev-parse origin/main
They should match.
git worktree remove <path>
If a stale entry is already marked prunable and its linked .git/gitdir is broken or missing, git worktree remove may fail validation instead of cleaning it up. In that case, do not force repeated removal attempts; rely on prune from the owning repo:
git worktree prune
After batch removal:
git worktree prune
Use non-force delete first:
git branch -d <branch>
If -d refuses, do not immediately keep the branch by default. First verify whether all of the following are true:
gh pr list --state all --head <branch> confirms the PR is already merged or closedgit cherry origin/main <branch> shows the branch is only carrying squash/rebase residue rather than active unpublished workWhen those checks confirm the branch is just post-merge local residue, force-delete is appropriate:
git branch -D <branch>
Only preserve the branch when those checks do not clearly prove it is stale.
Local backup/* branches often accumulate during repeated workspace cleanup and PR follow-up work. Do not treat every backup branch as automatically preservable just because it is not attached to a worktree.
For each backup branch, collect:
git rev-parse <backup-branch>
git show -s --format='%s' <backup-branch>
git rev-list --left-right --count origin/main...<backup-branch>
git cherry origin/main <backup-branch> || true
git branch --contains $(git rev-parse <backup-branch>)
env -u GITHUB_TOKEN gh pr list --state all --head <backup-branch> --json number,state,title,url,headRefName
Then validate portability with a disposable rebase worktree:
tmp=$(mktemp -d)
wt="$tmp/rebase-check"
git worktree add --detach "$wt" <backup-branch>
git -C "$wt" rebase origin/main
# if conflicting, inspect `git -C "$wt" status --short --branch` and abort
Interpretation:
skipped previously applied commit, the branch is effectively already absorbed by latest main and is staleUseful summary labels:
stale backup: absorbed by mainstale backup: redundant with kept branchkeep backup: latest representative of local lineuncertain backup: orphan local line with no clear replacementIn PR-heavy repositories, it is common to accumulate many backup/pr123-* branches that once preserved meaningful exploratory or follow-up work.
That historical meaning alone is not a reason to keep them forever.
After you have already done the branch-by-branch review, use this stricter cleanup rule:
Delete the backup branch when all are true:
MERGED or CLOSEDPractical interpretation:
historically meaningful is different from currently worth preserving locallyThis is especially useful after a cleanup audit where several stale backups were initially reported as meaningful historical lines but were later deleted because they had no open PR, no kept worktree, and no current local edits.
Run:
git rev-parse main
git rev-parse origin/main
git branch --show-current
git worktree list
Also report remaining dirty worktrees explicitly so the user can decide on follow-up cleanup.
.worktrees/ directoryA practical failure mode is to clean only git worktree list entries that happen to be under the repo root (for example .worktrees/...) and forget about sibling worktrees elsewhere in the workspace, such as:
/Users/.../corp-web-japan-querypie-ja-guardrails/Users/.../corp-web-japan-querypie-ja-skill/Users/.../corp-web-japan-t-cookie-preferenceThese still appear in git worktree list, but they are easy to overlook if you visually focus only on .worktrees/ paths.
Practical rule:
git worktree list --porcelain, regardless of whether the path is inside the repo root or in a sibling directory elsewhere in the workspaceAdditional practical rule:
pr318-*, even if they are not branch-backedTypical safe classification order:
git worktree list --porcelainpr318, pr-318, or pr_318Report:
main now matches origin/maingit fetch --prune can make many upstreams appear "gone"; do not treat that alone as permission to delete local branches.