Git Worktrees + Claude Code
I've been using Claude Code a lot. One friction point that kept bugging me: you're mid-session on a feature branch, Claude has a ton of context loaded, and then you need to switch branches. Maybe a bug came in, maybe you want to check something on main. So you stash, switch, lose all your context, and spend a few minutes getting back to where you were.
Git worktrees fix this. A worktree is just a second (or third, or fourth) checkout of your repo, pointed at a different branch. They all share the same .git history. The nice part: each one can run its own Claude Code session independently.
Bare repo setup
The trick is to use a bare repository as the base. Instead of one "real" checkout with worktrees hanging off it, you start bare and every branch is a worktree. You get a clean flat structure:
my-project/
.bare/ # shared git history
.git # pointer to .bare
main/ # worktree
feature-auth/ # worktree
fix-bug-123/ # worktree
No branch is special. Every worktree is a peer.
Shell helpers
I wrote a few functions to cut out the boilerplate. Drop this in ~/.git-worktree-tools.sh and source it:
#!/bin/bash
BARE_DIR=".bare"
# Clone a repo using the bare strategy
git-worktree-init() {
local url=$1 dir=$2
[[ -z "$url" || -z "$dir" ]] && echo "Usage: git-worktree-init <repo-url> <folder-name>" && return 1
mkdir -p "$dir" && cd "$dir"
git clone --bare "$url" "$BARE_DIR"
echo "gitdir: ./$BARE_DIR" > .git
git --git-dir="$BARE_DIR" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin
local main_branch=$(git --git-dir="$BARE_DIR" symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')
git worktree add "$main_branch" "$main_branch"
cd "$main_branch"
}
# Create a worktree (tracks remote branch if it exists, otherwise creates new)
gwa() {
local branch=$1
[[ -z "$branch" ]] && echo "Usage: gwa <branch-name>" && return 1
local project_root=$(git rev-parse --show-toplevel 2>/dev/null)
[[ -z "$project_root" ]] && echo "Not in a git repo" && return 1
local parent_dir=$(dirname "$project_root")
local target_path="$parent_dir/$branch"
if git rev-parse --verify "origin/$branch" >/dev/null 2>&1; then
git worktree add "$target_path" "$branch" && cd "$target_path"
else
git worktree add -b "$branch" "$target_path" && cd "$target_path"
fi
}
# Interactive switcher (needs fzf)
gw() {
command -v fzf &>/dev/null || { echo "fzf required"; return 1; }
local selected=$(git worktree list | fzf --height 15% --reverse --header="Switch Worktree" | awk '{print $1}')
[[ -n "$selected" ]] && cd "$selected"
}
# Remove current worktree, optionally delete the branch
gwr() {
local current_dir=$(basename "$PWD")
local branch_name=$(git rev-parse --abbrev-ref HEAD)
cd ..
git worktree remove "$current_dir"
echo -n "Delete branch '$branch_name'? (y/n): "
read -r answer
[[ "$answer" == [Yy]* ]] && git branch -D "$branch_name" && git push origin --delete "$branch_name" 2>/dev/null
git worktree prune
}
# Helpers
alias gwe='[[ -f ../.env ]] && ln -s ../.env .env && echo "Linked .env" || echo "No .env in parent."'
alias gmain='cd $(git worktree list | grep -E "\[(main|master)\]" | head -n 1 | awk "{print \$1}")'
alias gwp='git worktree prune && echo "Pruned."'Quick rundown:
git-worktree-init <url> <name>— sets up a new project with the bare repo patterngwa <branch>— creates a worktree (and branch if needed), cds into itgw— fuzzy-find between your worktreesgwr— tears down the current worktree and cleans upgmain— jump to the main branchgwe— symlink a shared.envfrom the parent directory
How I actually use this
The day-to-day looks like: I gwa feature-whatever, open Claude Code, and start working. If something comes up — a bug, a PR review, whatever — I open another terminal, gwa into a new worktree, and spin up a second Claude Code session there. The first one keeps running untouched.
During bigger projects I'll have three or four worktrees going. One for the API, one for the frontend, maybe one for tests. Each Claude session stays focused on its piece. When things are ready I merge them together.
The thing that makes this actually worth doing vs. just switching branches: Claude Code's context stays intact. You don't lose the conversation, you don't have to re-explain what you're building. Each session just keeps going in its own directory.
Practical notes
- Install fzf (
brew install fzf) — thegwswitcher is great - Use tmux or terminal tabs — one pane per worktree
- Different ports per worktree if you run dev servers
gwesaves you from copying.envfiles around- Don't hoard worktrees —
gwrcleans up fast, and branches are on the remote if you need them again