2026-03-29
Git comes with so many features in them, I will likely never know or get to use all of them in my lifetime. One such feature that has been gaining traction is Git Worktree. Riding the industrial seismic shift to Claude Code and other agentic coding tools, it has allowed engineers to keep working on multiple branches at the same time without needing to git stash; git fetch; git checkout -b; git stash pop; repeatedly.
This is one of my daily workflows, in which the 2 scripts I am sharing today will be incredibly helpful for:
bugfix/integer-overflow-abc123 for example.cd ~/org/project-1 and worktree-checkout bugfix/integer-overflow-abc123~/org/project-1__bugfix-integer-overflow-abc123, complete with all the gitignored files, such as .env .mcp.json .claude/* etc.bin/dev and it spins up development server on a different port number than my other worktrees, enabling Claude Code and I to work and test on different things simultaneously.All the codes below I am sharing to you are generated by Claude Code, but I believe this workflow I evolved over time will be helpful to you. Feel free to get your agent to look at it and adapt to your needs.
You can copy this function into your shell rc file, what this does:
git fetch to pull the latest branches.Checkout the branch to a directory same level as the project dir
project-1 # master branch
project-1__feature-add-something # feature/add-something
project-1__bugfix-fix-something # bugfix/fix-something
Code:
# ~/.zshrc
worktree-checkout() {
if [ -z "$1" ]; then
echo "Usage: worktree-checkout <branch-name>"
return 1
fi
local branch="$1"
local repo_dir="$(basename "$(pwd)")"
local sanitized="$(echo "$branch" | tr '/' '-')"
local worktree_path="../${repo_dir}__${sanitized}"
git fetch || return 1
git worktree add "$worktree_path" -b "$branch" "origin/$branch" 2>/dev/null \
|| git worktree add "$worktree_path" "$branch" \
|| return 1
cd "$worktree_path"
}
The .git directory is usually hidden in text editors and IDEs, you can access it with command line:
# ALWAYS modify .git only in the main worktree
cd ~/org/project-1
touch .git/hooks/post-checkout
# Substitute code with other IDE, if you aren't using vs code
code .git/hooks/post-checkout
This is my post-checkout hook:
#!/bin/bash
# Git post-checkout hook to automatically symlink/copy environment files to new worktrees
# This hook runs after git checkout, git switch, and git worktree add
# CONFIGURATION: Modify these lists for your project
# Files/directories to symlink (relative to repo root)
SYMLINK_FILES=(
".env"
".env.test"
".mcp.json"
".claude"
)
# Files/directories to copy (relative to repo root)
# Here you put the files that can deviate from main/branch worktrees
COPY_FILES=(
"tmp/tunnel"
"tmp/cloudflared-config.yml"
)
# ============================================================================
prev_head="$1"
new_head="$2"
branch_checkout="$3"
# Check if this is a worktree creation (previous HEAD is null)
if [[ "$prev_head" == "0000000000000000000000000000000000000000" ]] && [[ "$branch_checkout" == "1" ]]; then
echo "🔧 New worktree detected - setting up environment files..."
# Get the main repository path
main_repo_path="$(git rev-parse --git-common-dir)/.."
main_repo_path="$(cd "$main_repo_path" && pwd)"
# Get current worktree path
current_path="$(pwd)"
echo "📁 Copying from: $main_repo_path"
echo "📁 Copying to: $current_path"
# Create necessary directories from COPY_FILES
while IFS= read -r dir; do
if [[ ! -d "$current_path/$dir" ]]; then
mkdir -p "$current_path/$dir"
echo "📁 Created $dir"
fi
done < <(printf '%s\n' "${COPY_FILES[@]}" | xargs -I {} dirname {} | sort -u)
# Symlink files/directories
for item in "${SYMLINK_FILES[@]}"; do
if [[ -e "$main_repo_path/$item" ]]; then
# Remove existing file/directory first to avoid nested symlinks
rm -rf "$current_path/$item"
ln -s "$main_repo_path/$item" "$current_path/$item"
echo "✅ Symlinked $item"
else
echo "⚠️ $item not found in main repository"
fi
done
# Copy files
for file in "${COPY_FILES[@]}"; do
source_file="$main_repo_path/$file"
target_file="$current_path/$file"
if [[ -f "$source_file" ]]; then
mkdir -p "$(dirname "$target_file")"
cp "$source_file" "$target_file"
echo "✅ Copied $file"
else
echo "⚠️ $file not found in main repository"
fi
done
# Generate a random port between 3000-3999 and write to tmp/port
PORT=$(( (RANDOM % 1000) + 3000 ))
echo "$PORT" > tmp/port
echo "✅ Generated random port: $PORT"
echo "🎉 Environment setup complete for new worktree!"
fi
In post-checkout hook, we generate random number into tmp/port file to avoid port collision between worktrees. Now you need to ensure whatever script you use to boot up development server reads from that port file. For example, bin/dev:
#!/usr/bin/env sh
# Read .port file, else defaults to 3000
PORT=${PORT:-$(test -f tmp/port && cat tmp/port || echo 3000)}
bin/rails s -p $PORT
The problem I have with this workflow is not the tech, but me - I have trouble juggling this much work in my biological context window.
We are judged by others with our output quality, so even with all these new agentic coding workflows, we should be careful and not get too carried away of playing the token slot machines and lose track of our work.