Skip to main content

Worktree Parallel Work

Git worktrees let you check out multiple branches of the same repo simultaneously, each in its own directory. CodeLeash's init.sh script automatically configures isolated ports and Supabase instances for each worktree, so multiple branches can run side by side without conflicts.

Worktree Detection

The init.sh script compares the current directory to the main repo and calculates a slot number:

WORKTREE_NAME=$(basename "$PWD")
MAIN_REPO=$(git worktree list | head -1 | awk '{print $1}')

if [ "$PWD" = "$MAIN_REPO" ]; then
SLOT=0
PROJECT_ID="codeleash"
else
# Calculate slot from worktree name
if [[ "$WORKTREE_NAME" =~ ^[0-9]+$ ]] && [ "$WORKTREE_NAME" -ge 1 ] && [ "$WORKTREE_NAME" -le 99 ]; then
SLOT=$WORKTREE_NAME
else
SLOT=$(echo -n "$WORKTREE_NAME" | cksum | awk '{print ($1 % 99) + 1}')
fi
fi

init.sh

  • The main repo always gets slot 0 (default ports)
  • Numeric worktree names (1-99) use their number as the slot directly
  • Other names are hashed with cksum to a slot in 1-99

Port Formula

Each slot gets a deterministic set of ports, calculated with simple arithmetic:

PORT=$((8000 + SLOT))
VITE_PORT=$((5173 + SLOT))
API_PORT=$((54321 + SLOT * 10))
DB_PORT=$((54322 + SLOT * 10))
SHADOW_PORT=$((54320 + SLOT * 10))
POOLER_PORT=$((54329 + SLOT * 10))
STUDIO_PORT=$((54323 + SLOT * 10))
INBUCKET_PORT=$((54324 + SLOT * 10))

init.sh

ServiceFormulaSlot 0 (main)Slot 1Slot 5
FastAPI8000 + slot800080018005
Vite5173 + slot517351745178
Supabase API54321 + slot×10543215433154371
Supabase DB54322 + slot×10543225433254372
DB Shadow54320 + slot×10543205433054370
DB Pooler54329 + slot×10543295433954379
Studio54323 + slot×10543235433354373
Inbucket54324 + slot×10543245433454374
Analytics54327 + slot×10543275433754377

Supabase Config Isolation

For worktrees (slot > 0), init.sh generates a fresh config and patches the ports with sed:

# Generate fresh config.toml
TEMP_DIR=$(mktemp -d)
(cd "$TEMP_DIR" && supabase init --force) > /dev/null 2>&1
cp "$TEMP_DIR/supabase/config.toml" "$TEMP_CONFIG"

# Patch port numbers
sed -i '' "s/^project_id = .*/project_id = \"$PROJECT_ID\"/" "$TEMP_CONFIG"
sed -i '' "s/^port = 54321$/port = $API_PORT/" "$TEMP_CONFIG"
sed -i '' "s/^port = 54322$/port = $DB_PORT/" "$TEMP_CONFIG"
sed -i '' "s/^shadow_port = 54320$/shadow_port = $SHADOW_PORT/" "$TEMP_CONFIG"
sed -i '' "s/^port = 54329$/port = $POOLER_PORT/" "$TEMP_CONFIG"
sed -i '' "s/^port = 54323$/port = $STUDIO_PORT/" "$TEMP_CONFIG"

init.sh

This ensures each worktree's Supabase instance has its own Docker containers and PostgreSQL data.

Environment File

Worktrees get their own .env with port overrides:

# Worktree 'feature-xyz' (slot 42) port configuration
PORT=8042
VITE_SERVER_PORT=5215
SUPABASE_URL=http://127.0.0.1:54741
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54742/postgres

The .env file starts as a copy from the main repo, with port-related variables replaced.

Typical Workflow

# Create a worktree for a feature branch
git worktree add ../my-feature feature-branch

# Initialize the worktree (installs deps, configures ports, starts Supabase)
cd ../my-feature
./init.sh

# Develop normally --- runs on its own ports
npm run dev # FastAPI on 8042, Vite on 5215

# Meanwhile, main repo keeps running on default ports
cd ../CodeLeash
npm run dev # FastAPI on 8000, Vite on 5173

Both instances run simultaneously with no port conflicts.

Limitations

  • Slot collisions: Two worktree names that hash to the same slot will conflict. Use numeric names (1-99) for deterministic assignment.
  • Docker resources: Each Supabase instance runs its own set of Docker containers. Running many worktrees simultaneously requires significant memory.
  • First startup: The first init.sh in a worktree may need to pull Docker images, which can be slow.
  • macOS sed: The port patching uses sed -i '' (BSD sed syntax). On Linux, this would need sed -i without the empty string argument.