When working with Git worktrees, environment files (.env, .env.local, etc.) are not automatically copied from the main repository to new worktrees. This causes issues because:
- ❌ New worktrees can't run the project without
.envfiles - ❌ You have to manually copy
.envfiles every time you create a worktree - ❌ Easy to forget, leading to confusing errors when trying to test
- ❌ Especially problematic with Claude Code which creates worktrees automatically
Set up a global Git hook that automatically copies all .env* files from the main repository to any new worktree when it's created.
git config --global core.hooksPathIf this returns a path:
- You already have a global hooks directory configured
- Use that path instead of
~/.git-hooksin the steps below - Skip to Step 3
If this returns nothing (or an error):
- No global hooks configured yet
- Proceed to Step 2
# Create the directory
mkdir -p ~/.git-hooks
# Configure Git to use it globally
git config --global core.hooksPath ~/.git-hooks
# Verify it's set
git config --global core.hooksPath
# Should output: /Users/yourname/.git-hooksRun this command to create the hook file:
cat > ~/.git-hooks/post-checkout << 'EOF'
#!/bin/bash
# Global Git hook: post-checkout
# Automatically copy .env files to new worktrees
# Only run for worktree checkouts (not regular branch switches in main repo)
# $3 = 1 means it's a branch checkout (not file checkout)
if [ "$3" != "1" ]; then
exit 0
fi
# Get the common git directory (works for both main repo and worktrees)
GIT_COMMON_DIR=$(git rev-parse --git-common-dir)
MAIN_REPO=$(cd "$GIT_COMMON_DIR/.." && pwd)
CURRENT_DIR=$(pwd)
# If we're in a worktree (not the main repo), copy .env files
if [ "$CURRENT_DIR" != "$MAIN_REPO" ]; then
echo "📋 Worktree detected: $CURRENT_DIR"
# Copy all .env* files from main repo to worktree
for envfile in "$MAIN_REPO"/.env*; do
if [ -f "$envfile" ]; then
filename=$(basename "$envfile")
if [ ! -f "$CURRENT_DIR/$filename" ]; then
cp "$envfile" "$CURRENT_DIR/$filename"
echo " ✓ Copied $filename"
else
echo " ⊘ Skipped $filename (already exists)"
fi
fi
done
echo "✅ Environment files synced!"
fi
EOFchmod +x ~/.git-hooks/post-checkout# Check the hook exists and is executable
ls -la ~/.git-hooks/post-checkout
# Should show something like:
# -rwxr-xr-x 1 yourname staff XXX Jan 14 XX:XX /Users/yourname/.git-hooks/post-checkoutCreate a test worktree to verify it works:
# Navigate to any repo with .env files
cd /path/to/your/repo
# Create a test worktree
git worktree add ~/.test-worktree -b test-branch
# You should see output like:
# Preparing worktree (new branch 'test-branch')
# HEAD is now at XXXXXXX <commit message>
# 📋 Worktree detected: /Users/yourname/.test-worktree
# ✓ Copied .env
# ✓ Copied .env.local
# ✅ Environment files synced!
# Verify files were copied
ls ~/.test-worktree/.env*
# Clean up
git worktree remove ~/.test-worktree
git branch -D test-branch- Trigger: When you run
git worktree add, Git executes thepost-checkouthook - Detection: The hook detects if you're in a worktree (not the main repo)
- Copy: It finds all
.env*files in the main repository - Sync: Copies them to the new worktree (skips if they already exist)
All files matching .env* pattern:
.env.env.local.env.development.env.production.env.test.env.example- etc.
- ✅ Won't overwrite: If a file already exists in the worktree, it's skipped
- ✅ Only worktrees: Won't affect your main repository
- ✅ Only on creation: Only runs when checking out a new worktree, not on branch switches
- ✅ Global: Works for all repositories on your machine
✅ Zero manual copying: Environment files are automatically synced to new worktrees
✅ Works with Claude Code: Claude creates worktrees automatically, and this hook ensures they have the necessary .env files
✅ Works with manual worktrees: Also works when you create worktrees manually with git worktree add
✅ Set once, use everywhere: Global hook applies to all repositories on your machine
✅ No maintenance: Set it up once and it works forever
Check if global hooks path is set:
git config --global core.hooksPathIf not set, configure it:
git config --global core.hooksPath ~/.git-hooksCheck if hook is executable:
ls -la ~/.git-hooks/post-checkout
# Should show -rwxr-xr-x (note the 'x' for executable)If not executable, fix it:
chmod +x ~/.git-hooks/post-checkoutVerify .env files exist in main repo:
cd /path/to/main/repo
ls -la .env*Test hook manually in a worktree:
cd /path/to/worktree
bash -x ~/.git-hooks/post-checkout 0 0 1This will show debug output to help identify the issue.
In worktrees, git rev-parse --git-dir returns .git/worktrees/<worktree-name>, which is the worktree-specific git directory. If we use cd "$GIT_DIR/..", we end up in .git/worktrees/ instead of the main repo root.
Using --git-common-dir always points to the main .git directory, so cd "$GIT_COMMON_DIR/.." correctly resolves to the main repository root where .env files are stored.
Incorrect approach (buggy):
GIT_DIR=$(git rev-parse --git-dir)
MAIN_REPO=$(cd "$GIT_DIR/.." && pwd)
# Results in: /path/to/repo/.git/worktrees (WRONG!)Correct approach:
GIT_COMMON_DIR=$(git rev-parse --git-common-dir)
MAIN_REPO=$(cd "$GIT_COMMON_DIR/.." && pwd)
# Results in: /path/to/repo (CORRECT!)After setting this up, you can:
- Create worktrees with
git worktree addand environment files will be automatically copied - Use Claude Code without worrying about missing
.envfiles - Test features in isolated worktrees without manual setup
If you prefer, you can run this all-in-one script:
# Create global hooks directory if it doesn't exist
mkdir -p ~/.git-hooks
# Create the post-checkout hook
cat > ~/.git-hooks/post-checkout << 'EOF'
#!/bin/bash
if [ "$3" != "1" ]; then exit 0; fi
GIT_COMMON_DIR=$(git rev-parse --git-common-dir)
MAIN_REPO=$(cd "$GIT_COMMON_DIR/.." && pwd)
CURRENT_DIR=$(pwd)
if [ "$CURRENT_DIR" != "$MAIN_REPO" ]; then
echo "📋 Worktree detected: $CURRENT_DIR"
for envfile in "$MAIN_REPO"/.env*; do
if [ -f "$envfile" ]; then
filename=$(basename "$envfile")
if [ ! -f "$CURRENT_DIR/$filename" ]; then
cp "$envfile" "$CURRENT_DIR/$filename"
echo " ✓ Copied $filename"
else
echo " ⊘ Skipped $filename (already exists)"
fi
fi
done
echo "✅ Environment files synced!"
fi
EOF
# Make it executable
chmod +x ~/.git-hooks/post-checkout
# Configure Git to use global hooks
git config --global core.hooksPath ~/.git-hooks
echo "✅ Global Git worktree .env auto-copy hook installed!"If you run into issues, check:
- Global hooks path is configured:
git config --global core.hooksPath - Hook file exists:
ls ~/.git-hooks/post-checkout - Hook is executable:
ls -la ~/.git-hooks/post-checkout .envfiles exist in main repo:ls -la .env*