Git Worktrees for LLM Multitasking

/ developer-toolsgittmuxai

I’ve been using Claude Code as my primary coding assistant for a while now. It’s good at staying focused on a single task — but real work is rarely a single task. You’re halfway through a feature when a bug report lands. You want to prototype something without derailing your current branch. You want three Claude sessions running in parallel, each working on something different.

The problem: switching branches mid-conversation blows up your AI context. The working tree changes, the files Claude was editing are gone or different, and you’re starting from scratch. Even if you stash and restore, the LLM has no idea what happened.

I needed a way to give each task its own isolated environment — branch, editor, terminal, and Claude instance — with one command to spin up and one to tear down.

The setup

The workflow pairs git worktrees with tmux sessions. Each worktree is a full checkout of the repo in a separate directory, sharing the same .git store. Each tmux session gets three windows: neovim, Claude Code, and a plain shell.

The commands are built on top of git-town for stacked branch management. Here’s how it works.

hack <branch> — spin up a new task

hack feat/add-auth

This does four things:

  1. Creates a stacked branch via git town append (so it tracks the parent branch for easy rebasing later)
  2. Creates a worktree under .worktrees/ with a random adjective-animal name (like bold-falcon)
  3. Symlinks .claude/ into the worktree so permissions and settings carry over
  4. Launches a tmux session with three windows: nvim, claude, and zsh

The branch and worktree setup:

# Create stacked branch, then return to current branch
git town append "$BRANCH"
git checkout "$CURRENT"

# Generate a random worktree name like "bold-falcon"
ADJECTIVES=(amber bold bright calm clever cool cosmic crisp ...)
ANIMALS=(badger bear cat crane crow deer dolphin eagle ...)
WORKTREE_NAME="${adj}-${animal}"

mkdir -p "$REPO_ROOT/.worktrees"
git worktree add "$REPO_ROOT/.worktrees/$WORKTREE_NAME" "$BRANCH"

# Symlink .claude so the worktree inherits permissions and settings
if [ -d "$REPO_ROOT/.claude" ] && [ ! -e "$WORKTREE_PATH/.claude" ]; then
  ln -s "$REPO_ROOT/.claude" "$WORKTREE_PATH/.claude"
fi

The tmux session gets three windows — an editor, a Claude Code instance, and a shell:

# Name the session <parent>/<branch> for easy identification
PARENT_SESSION=$(tmux display-message -p '#S')
SESSION="${PARENT_SESSION}/${BRANCH}"

tmux new-session -d -s "$SESSION" -n "nvim" -c "$WORKTREE_PATH"
tmux send-keys -t "$SESSION:nvim" "nvim" Enter

tmux new-window -t "$SESSION" -n "claude" -c "$WORKTREE_PATH"
tmux send-keys -t "$SESSION:claude" "claude" Enter

tmux new-window -t "$SESSION" -n "zsh" -c "$WORKTREE_PATH"

tmux select-window -t "$SESSION:nvim"

The worktree directory naming is intentionally disconnected from the branch name. Branch names change, get long, and have slashes. A name like bold-falcon is easy to type and doesn’t collide.

If the branch or worktree already exists, hack reuses them and just switches to the session. So it’s safe to run repeatedly.

hacks — see what’s running

hacks
Branch                   Worktree           Session                        Status
------                   --------           -------                        ------
feat/add-auth            bold-falcon        work/feat/add-auth             active
fix/null-pointer         calm-otter         work/fix/null-pointer          active
refactor/api-client      swift-heron        -                              no session

Lists all worktrees with their branch, tmux session, and status. Quick way to see what you have going.

rehack — restore after restart

rehack

After a reboot, your tmux sessions are gone but the worktrees survive on disk. rehack scans .worktrees/, finds any without a tmux session, and recreates the three-window layout for each one:

for wt_dir in "$WORKTREES_DIR"/*/; do
  wt_dir="${wt_dir%/}"
  [ -d "$wt_dir" ] || continue

  BRANCH=$(find_branch_for_worktree "$wt_dir" || true)

  # Skip worktrees that already have a tmux session
  EXISTING=$(find_sessions_for_worktree "$wt_dir")
  if [ -n "$EXISTING" ]; then
    echo "Skip: $WORKTREE_NAME ($BRANCH) — session already exists"
    continue
  fi

  # Recreate the same 3-window layout as hack
  tmux new-session -d -s "$SESSION" -n "nvim" -c "$wt_dir"
  tmux send-keys -t "$SESSION:nvim" "nvim" Enter
  tmux new-window -t "$SESSION" -n "claude" -c "$wt_dir"
  tmux send-keys -t "$SESSION:claude" "claude" Enter
  tmux new-window -t "$SESSION" -n "zsh" -c "$wt_dir"
done

It’s idempotent — already-active sessions are skipped. This is the command that makes the whole workflow viable long-term. Without it, a restart would mean manually recreating every session.

swap / unswap — for single-instance apps

swap feat/add-auth
# ... run dev server, test, etc.
unswap

Some projects can’t run multiple instances — the dev server binds to a fixed port, or there’s a lockfile. swap temporarily moves a worktree’s branch into the main directory:

# Stash uncommitted changes in both locations
git stash push -m "swap: auto-stash for $MAIN_BRANCH"
git -C "$WORKTREE_PATH" stash push -m "swap: auto-stash for $BRANCH"

# Detach the worktree so the branch is free
git -C "$WORKTREE_PATH" checkout --detach

# Check out the feature branch in main
git checkout "$BRANCH"

# Save state so unswap knows how to reverse everything
cat > "$SWAP_STATE" << EOF
SWAP_BRANCH=$BRANCH
SWAP_WORKTREE_PATH=$WORKTREE_PATH
SWAP_MAIN_BRANCH=$MAIN_BRANCH
SWAP_MAIN_STASH_SHA=$MAIN_STASH_SHA
SWAP_WORKTREE_STASH_SHA=$WORKTREE_STASH_SHA
EOF

unswap reverses everything: restores the original branch in main, reattaches the worktree, and pops stashes back where they belong. Any new work done in the main directory during the swap gets stashed and transferred to the worktree automatically.

unhack <branch> — clean teardown

unhack feat/add-auth

After a branch is merged:

# Guard against unhacking a swapped branch
if [ -f "$SWAP_STATE" ]; then
  source "$SWAP_STATE"
  if [ "$SWAP_BRANCH" = "$BRANCH" ]; then
    echo "Error: branch '$BRANCH' is currently swapped into the main directory."
    echo "Run 'unswap' before running 'unhack'."
    exit 1
  fi
fi

# Find and kill tmux sessions associated with this worktree
SESSIONS=$(find_sessions_for_worktree "$WORKTREE_PATH")
while IFS= read -r session; do
  # If we're inside the target session, switch away first
  if [ -n "$TMUX" ]; then
    CURRENT_SESSION=$(tmux display-message -p '#S')
    if [ "$CURRENT_SESSION" = "$session" ]; then
      OTHER=$(tmux list-sessions -F '#S' | grep -v "^${session}$" | head -1)
      tmux switch-client -t "$OTHER"
    fi
  fi
  tmux kill-session -t "$session"
done <<< "$SESSIONS"

# Remove worktree and optionally clean up the branch
git worktree remove "$WORKTREE_PATH"
git merged "$BRANCH"  # rebases children onto main, deletes merged branch

The teardown handles edge cases: it switches you out of the session before killing it, and delegates branch cleanup to git merged which rebases any child branches onto main before deleting.

Why this matters for LLM workflows

The real payoff is context isolation for AI coding assistants. Each hack session gives Claude Code:

  • Its own working directory — file edits in one session don’t affect another
  • Its own branch — commits are isolated, no merge conflicts between parallel work
  • Its own conversation — each Claude instance maintains independent context about what it’s working on
  • Instant switchingtmux switch-client is instantaneous, no branch checkout needed

I regularly run three or four Claude sessions in parallel. One is building a feature, another is fixing a bug, a third is writing tests. I check in on each one, give feedback, and move on. The worktree isolation means they never step on each other.

The rehack command is especially important here. When you restart your machine, you don’t want to re-explain context to every Claude session. The worktrees are intact on disk, so when Claude starts back up in the right directory, it can pick up where it left off (especially with conversation resumption).

Try it out

The scripts are all in my dotfiles repo under roles/zsh/files/bin/. They’re pure bash with no dependencies beyond git, tmux, and git-town.

The core idea is simple: worktrees give you directory-level isolation for free, and tmux gives you session management. The scripts just wire them together with sensible defaults. If you’re using an LLM coding assistant and find yourself fighting branch switches, give worktrees a try.