Skip to content
Fran Gonzalez
← Back to blog
(updated Aug 11, 2026)·Clanker·4 min read

Where to put git worktrees when you have many repos

Comparing three git worktree layouts for many local repos, and why I settled on ~/worktrees/<repo>/<slug>.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

I use git worktrees to work on multiple branches of a repo in parallel, and across a dozen-plus repos the question of where those worktree directories live started to matter.

The Problem

I had worktrees scattered as siblings of their checkouts, all sharing one org-mirrored parent directory (github.com/<org>/ and gitlab.com/<org>/). Three things broke.

  1. Collisions and litter. A worktree named feat-audit-gate from one repo sat next to feat-audit-gate from another, and the parent dir accumulated stray entries like feat/ that no longer belonged to any repo.
  2. Ambiguity. With worktrees named by branch slug and repos named by project, a directory like foo-charts could be a repo or a worktree of foo. That is exactly the confusion sellout’s worktree gist calls out.
  3. No single place to manage them. Listing, pruning, and backing up worktrees meant scanning a shared directory mixed with real checkouts.

I wanted a layout chosen on merit for many repos, not one inherited from how I happened to start.

What Changed

I compared three layouts on structural criteria (working-tree isolation, collision-free scaling, clean-by-default tooling, and backup scoping) rather than on habit.

LayoutWorking-tree isolationScales across reposClean by default
~/worktrees/<repo>/<slug> (centralized)fully outsiderepo-namespacedyes
../<repo>/<slug> (sibling of checkout)outsideshared parent pollutesmixed into project tree
../<repo>/.worktrees/<slug> (nested, hidden)inside the checkoutper-checkoutnoisy unless configured

I went with the centralized one: ~/worktrees/<repo>/<type>-<slug>. The root defaults to ~/worktrees and follows $WORKTREES if set; the per-worktree directory flattens the branch’s slash (feat/new becomes feat-new).

Two facts decided it. First, git allows a worktree inside another worktree’s working tree, but it then shows as untracked in git status and needs a .gitignore entry. commandcode.ai’s worktree guide puts the recommendation plainly: keep worktrees outside the repo so they never nest, never appear in the file tree, and never need ignoring. Second, standard tooling (grep -r, find, editor indexers, fuzzy finders) descends into nested directories by default; a nested worktree means opting out of noise everywhere, while a centralized ~/worktrees means opting in to a scope only when you want it.

The canonical checkouts stay exactly where they were: ~/Documents/Projects/github.com/<org>/<repo> and ~/work/gitlab.com/<org>/<repo>. Only the worktrees move.

Results

Git links a worktree to its repo by metadata, not by path proximity, so the worktree can live anywhere on disk. I verified the three things that mattered for the switch:

# A worktree at an absolute path far from the repo just works
git worktree add -b feat/new ~/worktrees/myrepo/new-style

# `git worktree list` shows mixed locations: old siblings and new ones under ~/worktrees coexist
git worktree list

# Relocate an existing sibling worktree under ~/worktrees without rebuilding it
git worktree move ../myrepo-old-style ~/worktrees/myrepo/old-style

The practical upshot: I didn’t have to migrate anything. Existing sibling worktrees keep working where they are, registered in the same repo’s .git/worktrees/. New ones go to ~/worktrees/<repo>/. The list shows both, and my editor’s worktree switcher, which enumerates git worktree list --porcelain and jumps to the absolute path, handles them identically. When an old worktree’s branch is done, the next one is recreated under ~/worktrees/<repo>/, or git worktree move relocates it in place.

The one gotcha I hit: my switcher labeled entries as ../<basename>, which is accurate for sibling worktrees but wrong for ~/worktrees/... ones. A one-line fix to abbreviate the path relative to home (vim.fn.fnamemodify(path, ":~")) resolved it for both.

There’s a stronger pattern I haven’t adopted: colocation, which joncardasis’s gist calls the “gold standard”. Instead of a normal clone plus separate worktrees, you keep one bare repository (a git database with no working tree of its own) and add every branch, including main, as a linked worktree beside it. Because nothing is checked out at the repository root, no branch is privileged: the main worktree can be removed as freely as any other, which also sidesteps the rule that a branch checked out in one worktree is locked there. It’s an upgrade I can move to later without changing the ~/worktrees/<repo>/ path.

References

This post was written with AI assistance.