Loading...
Loading...
Manage parallel development with Git worktrees. Covers worktree creation with port allocation, environment sync, branch isolation for multi-agent workflows, cleanup automation, and Docker Compose integration. Use when working on multiple branches simultaneously, running parallel CI validations, or isolating agent workspaces.
npx skill4agent add borghei/claude-skills git-worktree-manager.worktree-ports.jsonrm -rf# Create worktree for a new feature branch
git worktree add ../wt-auth -b feature/new-auth main
# Create worktree from an existing branch
git worktree add ../wt-hotfix hotfix/fix-login
# Create worktree in a dedicated directory
git worktree add ~/worktrees/myapp-auth -b feature/auth origin/maingit worktree list
# Output:
# /Users/dev/myapp abc1234 [main]
# /Users/dev/wt-auth def5678 [feature/new-auth]
# /Users/dev/wt-hotfix ghi9012 [hotfix/fix-login]# Safe removal (fails if there are uncommitted changes)
git worktree remove ../wt-auth
# Force removal (discards uncommitted changes)
git worktree remove --force ../wt-auth
# Prune stale metadata
git worktree pruneWorktree Index App Port DB Port Redis Port API Port
────────────────────────────────────────────────────────────────
0 (main) 3000 5432 6379 8000
1 (wt-auth) 3010 5442 6389 8010
2 (wt-hotfix) 3020 5452 6399 8020
3 (wt-feature) 3030 5462 6409 8030port = base_port + (worktree_index * stride).worktree-ports.json{
"worktree": "wt-auth",
"branch": "feature/new-auth",
"index": 1,
"ports": {
"app": 3010,
"database": 5442,
"redis": 6389,
"api": 8010
},
"created": "2026-03-09T10:30:00Z"
}# Check if a port is already in use
check_port() {
local port=$1
if lsof -i :"$port" > /dev/null 2>&1; then
echo "PORT $port is BUSY"
return 1
else
echo "PORT $port is FREE"
return 0
fi
}
# Check all ports for a worktree
for port in 3010 5442 6389 8010; do
check_port $port
done#!/bin/bash
# setup-worktree.sh — Create a fully prepared worktree
set -euo pipefail
BRANCH="${1:?Usage: setup-worktree.sh <branch-name> [base-branch]}"
BASE="${2:-main}"
WT_NAME="wt-$(echo "$BRANCH" | sed 's|.*/||' | tr '[:upper:]' '[:lower:]')"
WT_PATH="../$WT_NAME"
MAIN_REPO="$(git rev-parse --show-toplevel)"
echo "Creating worktree: $WT_PATH from $BASE..."
# 1. Create worktree
if git rev-parse --verify "$BRANCH" > /dev/null 2>&1; then
git worktree add "$WT_PATH" "$BRANCH"
else
git worktree add "$WT_PATH" -b "$BRANCH" "$BASE"
fi
# 2. Copy environment files
for envfile in .env .env.local .env.development; do
if [ -f "$MAIN_REPO/$envfile" ]; then
cp "$MAIN_REPO/$envfile" "$WT_PATH/$envfile"
echo "Copied $envfile"
fi
done
# 3. Allocate ports
WT_INDEX=$(git worktree list | grep -n "$WT_PATH" | cut -d: -f1)
WT_INDEX=$((WT_INDEX - 1))
STRIDE=10
cat > "$WT_PATH/.worktree-ports.json" << EOF
{
"worktree": "$WT_NAME",
"branch": "$BRANCH",
"index": $WT_INDEX,
"ports": {
"app": $((3000 + WT_INDEX * STRIDE)),
"database": $((5432 + WT_INDEX * STRIDE)),
"redis": $((6379 + WT_INDEX * STRIDE)),
"api": $((8000 + WT_INDEX * STRIDE))
}
}
EOF
echo "Ports allocated (index $WT_INDEX)"
# 4. Update .env with allocated ports
if [ -f "$WT_PATH/.env" ]; then
APP_PORT=$((3000 + WT_INDEX * STRIDE))
DB_PORT=$((5432 + WT_INDEX * STRIDE))
sed -i.bak "s/APP_PORT=.*/APP_PORT=$APP_PORT/" "$WT_PATH/.env"
sed -i.bak "s/:5432/:$DB_PORT/g" "$WT_PATH/.env"
rm -f "$WT_PATH/.env.bak"
echo "Updated .env with worktree ports"
fi
# 5. Install dependencies
cd "$WT_PATH"
if [ -f "pnpm-lock.yaml" ]; then
pnpm install --frozen-lockfile
elif [ -f "package-lock.json" ]; then
npm ci
elif [ -f "yarn.lock" ]; then
yarn install --frozen-lockfile
elif [ -f "requirements.txt" ]; then
pip install -r requirements.txt
elif [ -f "go.mod" ]; then
go mod download
fi
echo ""
echo "Worktree ready: $WT_PATH"
echo "Branch: $BRANCH"
echo "App port: $((3000 + WT_INDEX * STRIDE))"
echo ""
echo "Next: cd $WT_PATH && pnpm dev"# docker-compose.worktree.yml — override for worktree-specific ports
# Usage: docker compose -f docker-compose.yml -f docker-compose.worktree.yml up
services:
postgres:
ports:
- "${DB_PORT:-5432}:5432"
environment:
POSTGRES_DB: "myapp_${WT_NAME:-main}"
redis:
ports:
- "${REDIS_PORT:-6379}:6379"
app:
ports:
- "${APP_PORT:-3000}:3000"
environment:
DATABASE_URL: "postgresql://dev:dev@postgres:5432/myapp_${WT_NAME:-main}"DB_PORT=5442 REDIS_PORT=6389 APP_PORT=3010 WT_NAME=auth \
docker compose -f docker-compose.yml -f docker-compose.worktree.yml up -d#!/bin/bash
# cleanup-worktrees.sh — Safe worktree cleanup
set -euo pipefail
STALE_DAYS="${1:-14}"
DRY_RUN="${2:-true}"
echo "Scanning worktrees (stale threshold: ${STALE_DAYS} days)..."
echo ""
git worktree list --porcelain | while read -r line; do
case "$line" in
worktree\ *)
WT_PATH="${line#worktree }"
;;
branch\ *)
BRANCH="${line#branch refs/heads/}"
# Skip main worktree
if [ "$WT_PATH" = "$(git rev-parse --show-toplevel)" ]; then
continue
fi
# Check if branch is merged
MERGED=""
if git branch --merged main | grep -q "$BRANCH" 2>/dev/null; then
MERGED=" [MERGED]"
fi
# Check for uncommitted changes
DIRTY=""
if [ -d "$WT_PATH" ]; then
cd "$WT_PATH"
if [ -n "$(git status --porcelain)" ]; then
DIRTY=" [DIRTY - has uncommitted changes]"
fi
cd - > /dev/null
fi
# Check age
if [ -d "$WT_PATH" ]; then
AGE_DAYS=$(( ($(date +%s) - $(stat -f %m "$WT_PATH" 2>/dev/null || stat -c %Y "$WT_PATH" 2>/dev/null)) / 86400 ))
STALE=""
if [ "$AGE_DAYS" -gt "$STALE_DAYS" ]; then
STALE=" [STALE: ${AGE_DAYS} days old]"
fi
fi
echo "$WT_PATH ($BRANCH)$MERGED$DIRTY$STALE"
if [ -n "$MERGED" ] && [ -z "$DIRTY" ] && [ "$DRY_RUN" = "false" ]; then
echo " -> Removing merged clean worktree..."
git worktree remove "$WT_PATH"
fi
;;
esac
done
echo ""
git worktree prune
echo "Done. Run with 'false' as second arg to actually remove."Agent Assignment:
───────────────────────────────────────────────────
Agent 1 (Claude Code) → wt-feature-auth (port 3010)
Agent 2 (Cursor) → wt-feature-billing (port 3020)
Agent 3 (Copilot) → wt-bugfix-login (port 3030)
Main repo → integration (main) (port 3000)
───────────────────────────────────────────────────
Rules:
- Each agent works ONLY in its assigned worktree
- No agent modifies another agent's worktree
- Integration happens via PRs to main, not direct merges
- Port conflicts are impossible due to deterministic allocation| Scenario | Action |
|---|---|
| Need isolated dev server for a feature | Create a new worktree |
| Quick diff review of a branch | |
| Hotfix while feature branch is dirty | Create dedicated hotfix worktree |
| Bug triage with reproduction branch | Temporary worktree, cleanup same day |
| PR review with running code | Worktree at PR branch, run tests |
| Multiple agents on same repo | One worktree per agent |
git worktree list.worktree-ports.json.envpnpm install../wt-namegit worktree prunewt-<topic>.worktree-ports.json| Problem | Cause | Solution |
|---|---|---|
| Branch is already active in another worktree | Use |
| Port conflict despite deterministic allocation | A non-worktree process is occupying the assigned port | Run |
| Setup script was not run or | Copy |
| Worktree directory was deleted manually without | Run |
| Dependencies fail to install in new worktree | Lockfile references a private registry or cache not available in the worktree path | Ensure |
| Docker Compose services start on wrong ports | The | Always pass both files: |
| Worktree shows as dirty immediately after creation | Untracked files from | Add |
lsof.env.worktree-ports.json--forcepr-review-expertrelease-managerenv-secrets-managerci-cd-pipeline-buildermonorepo-navigator| Skill | Integration | Data Flow |
|---|---|---|
| Worktree setup copies | |
| CI pipelines can spin up worktrees for parallel test matrix execution | Pipeline config triggers |
| Release branches get dedicated worktrees for stabilization while feature work continues | Release worktree is created from the release branch; merged status drives cleanup automation |
| In monorepo setups, worktrees must respect package boundaries and shared dependencies | Worktree creation inherits the monorepo root lockfile; package-level dev servers use allocated port blocks |
| PR reviews can be performed in isolated worktrees with running code for manual validation | Reviewer creates a worktree at the PR branch, runs the dev server on allocated ports, and removes after review |
| Stale worktrees and abandoned branches surface as tech debt indicators | Cleanup script output feeds into debt tracking; worktree age and merge status inform priority scores |