A Git Worktree Workflow That Stays Tidy

A worktree workflow holds up if you follow four habits: keep worktrees in one predictable directory outside the repo, name the folder after the branch, script the setup that git doesn’t copy, and remove each worktree at merge time rather than later. Everything else is detail.

The loop, start to finish:

git worktree add ~/worktrees/myrepo/auth -b feature/auth
cd ~/worktrees/myrepo/auth && ./setup.sh
# ... work ...
git -C ~/code/myrepo merge feature/auth
git worktree remove ~/worktrees/myrepo/auth
git branch -d feature/auth

1. One place for worktrees, outside the repo

Two conventions work. Pick one and stop deciding.

Sibling folders, the default most tutorials show:

~/code/myrepo/
~/code/myrepo-auth/
~/code/myrepo-login/

Simple, and ../ paths are short. It clutters your projects directory once you have a few.

A dedicated worktree root, which scales better:

~/worktrees/myrepo/auth/
~/worktrees/myrepo/login/
~/worktrees/otherrepo/migration/

One place to look, one place to clean, and your projects directory stays a list of projects.

Never nest a worktree inside the repo it came from. It’s the single most expensive mistake here. Your editor treats it as part of the project, test runners glob into it, linters double-report, search returns everything twice, and file watchers index a second copy of the whole tree. JetBrains documents it as a break condition for its worktree integration, and every other tool degrades in its own way. If you must, at least add the path to .gitignore and your editor’s exclude list, and expect friction anyway.

2. Name the folder after the branch

The folder name is the only label you get. git worktree list shows path and branch together, and nothing else about what the work is.

git worktree add ~/worktrees/myrepo/fix-login -b fix/login-redirect

Slashes in branch names don’t translate to folders cleanly, so flatten them: fix/login-redirect becomes fix-login. The shorthand form (git worktree add ~/worktrees/myrepo/hotfix) creates a branch named after the last path component, which keeps the two in sync automatically. It’s a good default when the branch name doesn’t need a prefix.

Avoid generated or numbered names. wt-3 tells you nothing two days later, and it’s exactly what you’ll get if you let tooling name worktrees for you and never look.

3. Script what git doesn’t copy

A new worktree contains tracked files only. Not node_modules, not .env, not .venv, not build output, not local caches. Every fresh worktree starts unable to run.

Do it once, in a script committed to the repo:

#!/usr/bin/env bash
# setup.sh: run inside a fresh worktree
set -euo pipefail
MAIN="${1:-$HOME/code/myrepo}"
cp "$MAIN/.env" .
npm ci
npm run build:dev

Then a new worktree is two commands, and an agent can run the second one itself. Cursor’s .cursor/worktrees.json and Zed’s setup hooks exist for the same reason. See git worktrees in your editor.

Watch for anything that assumes a single checkout: fixed dev-server ports, a hardcoded database name, a lockfile in a shared cache directory, Docker container names. Two worktrees running at once will collide on all of them. Parameterise the port and the database name early, or accept that only one worktree can run the app at a time.

4. Remove at merge time

The cleanup step is where discipline actually breaks, because it happens after the interesting part is over.

git worktree remove ~/worktrees/myrepo/auth
git branch -d feature/auth

Treat those two lines as part of merging, not as a separate task. git branch -d refuses to delete unmerged work, so it doubles as a check that the merge actually happened.

Audit weekly:

git worktree list

Anything marked prunable is a directory you deleted by hand. git worktree prune clears those. Full detail in how to remove a git worktree.

Best practices, condensed

  • One worktree per task, not per branch you might get to. Worktrees are cheap on disk but expensive in attention.
  • Keep the main worktree on your integration branch. Do merges there, so main is always one cd away and never mid-experiment.
  • Commit before you remove. git worktree remove refuses when there are untracked or modified files, and --force is a good way to lose a .env you copied in.
  • Don’t share a worktree between two sessions. That reintroduces the silent live-file overwrites separate worktrees avoid. See running two agents on the same repo.
  • Detached HEAD for throwaway work. git worktree add -d ../poke v1.2.0 claims no branch and leaves nothing to clean up but a folder.
  • git worktree repair after moving folders in Finder or with mv. It reestablishes the links rather than making you recreate the worktrees.

Where the workflow breaks down

None of this is hard. It’s just repetitive, and the volume changes when agents are involved. One or two worktrees a week is a habit. A worktree per task with agents like Claude Code or Codex running several tasks at once is a dozen a day, each needing a folder, a branch, a setup run, a terminal, and a cleanup. That’s when the discipline above stops being enough on its own, and why Claude Code ships a --worktree flag to skip the plumbing.

crystl makes the whole loop structural. An isolated session creates the worktree and branch together, keeps them in a managed directory so nothing sprawls, shows which session belongs to which project, and prompts you to merge, keep, or discard when you close it. Branches that get parked stay reachable, with commit and change counts, so an abandoned worktree is something you can pick back up rather than something you find later and don’t recognise.

Next