Skip to content

Instantly share code, notes, and snippets.

@nicolasrouanne
Created February 17, 2026 15:44
Show Gist options
  • Select an option

  • Save nicolasrouanne/f1aa5458467642dec57a64ccea8ff754 to your computer and use it in GitHub Desktop.

Select an option

Save nicolasrouanne/f1aa5458467642dec57a64ccea8ff754 to your computer and use it in GitHub Desktop.
Git Worktree Manager — parallel dev environments with isolated Docker (ports, DB, containers)
#!/usr/bin/env bash
set -euo pipefail
# ════════════════════════════════════════════════════════════════════
# Git Worktree Manager
#
# Manages parallel development environments with isolated Docker
# containers, ports, databases, and volumes.
# ════════════════════════════════════════════════════════════════════
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Default port offsets from base
PORT_INCREMENT=1000
FIRST_BASE_PORT=4000
# Ports reserved by the OS (e.g. macOS AirPlay Receiver on 5000/7000)
SKIP_BASE_PORTS="5000 7000"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# ────────────────────────────────────────────────────────────────────
# Helpers
# ────────────────────────────────────────────────────────────────────
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die() { error "$@"; exit 1; }
usage() {
cat <<EOF
${BOLD}Usage:${NC} bin/worktree <command> [options]
${BOLD}Commands:${NC}
create <branch> [--base-port N] Create a worktree with Docker config
list List worktrees with ports and status
remove <name> [--force] Remove worktree + containers + volumes
status [name] Show detailed status
start [name] Start Docker containers
stop [name] Stop Docker containers
${BOLD}Examples:${NC}
bin/worktree create feature/auth
bin/worktree create feature/auth --base-port 5000
bin/worktree start feature-auth
bin/worktree stop feature-auth
bin/worktree remove feature-auth
bin/worktree list
${BOLD}Port allocation (increments of $PORT_INCREMENT from $FIRST_BASE_PORT):${NC}
Worktree 1: base=$FIRST_BASE_PORT → nginx=$((FIRST_BASE_PORT+1)), api=$FIRST_BASE_PORT, ...
Worktree 2: base=$((FIRST_BASE_PORT+PORT_INCREMENT)) → nginx=$((FIRST_BASE_PORT+PORT_INCREMENT+1)), ...
EOF
}
# Sanitize branch name to a valid worktree directory name
sanitize_name() {
echo "$1" | sed 's|/|-|g' | sed 's|[^a-zA-Z0-9_-]|-|g'
}
# Calculate ports from a base port
calc_ports() {
local base=$1
NGINX_PORT=$((base + 1))
API_PORT=$base
WEB_PORT=$((base + 2173))
CHAT_PORT=$((base + 5080))
POSTGRES_PORT=$((base + 2432))
REDIS_PORT=$((base + 3379))
DEBUG_API_PORT=$((base + 35697))
DEBUG_SIDEKIQ_PORT=$((base + 35698))
}
# Find the next available base port
find_next_base_port() {
local port=$FIRST_BASE_PORT
while true; do
local in_use=false
# Check all worktrees' .env.worktree files for this base port
while IFS= read -r wt_path; do
[ -z "$wt_path" ] && continue
local env_file="$wt_path/.env.worktree"
if [ -f "$env_file" ]; then
local existing_base
existing_base=$(grep "^API_PORT=" "$env_file" 2>/dev/null | cut -d= -f2)
if [ "$existing_base" = "$port" ]; then
in_use=true
break
fi
fi
done < <(git -C "$REPO_ROOT" worktree list --porcelain | grep "^worktree " | sed 's/^worktree //')
# Skip ports reserved by the OS
if echo "$SKIP_BASE_PORTS" | grep -qw "$port"; then
in_use=true
fi
if ! $in_use; then
echo "$port"
return
fi
port=$((port + PORT_INCREMENT))
done
}
# Check if a port is already bound on the host
check_port_available() {
local port=$1
if lsof -i :"$port" -sTCP:LISTEN >/dev/null 2>&1; then
return 1
fi
return 0
}
# Get the worktree path by name
get_worktree_path() {
local name="$1"
local worktrees_dir="$REPO_ROOT/.worktrees"
local wt_path="$worktrees_dir/$name"
if [ -d "$wt_path" ]; then
echo "$wt_path"
return 0
fi
# Try to find by matching in git worktree list
while IFS= read -r wt_path; do
[ -z "$wt_path" ] && continue
local wt_name
wt_name=$(basename "$wt_path")
if [ "$wt_name" = "$name" ]; then
echo "$wt_path"
return 0
fi
done < <(git -C "$REPO_ROOT" worktree list --porcelain | grep "^worktree " | sed 's/^worktree //')
return 1
}
# Resolve the real .git directory (handles worktrees where .git is a file)
resolve_git_dir() {
local repo="$1"
if [ -f "$repo/.git" ]; then
# Worktree: .git is a file pointing to the real git dir
local gitdir
gitdir=$(grep "^gitdir:" "$repo/.git" | sed 's/^gitdir: //')
# Resolve relative path
if [[ ! "$gitdir" = /* ]]; then
gitdir="$repo/$gitdir"
fi
# Go up from .git/worktrees/<name> to .git
echo "$(cd "$(dirname "$(dirname "$gitdir")")" && pwd)"
elif [ -d "$repo/.git" ]; then
echo "$repo/.git"
else
die "Cannot find .git in $repo"
fi
}
# ────────────────────────────────────────────────────────────────────
# Commands
# ────────────────────────────────────────────────────────────────────
cmd_create() {
local branch=""
local base_port=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--base-port)
base_port="$2"
shift 2
;;
-*)
die "Unknown option: $1"
;;
*)
if [ -z "$branch" ]; then
branch="$1"
else
die "Unexpected argument: $1"
fi
shift
;;
esac
done
[ -z "$branch" ] && die "Branch name required. Usage: bin/worktree create <branch> [--base-port N]"
local name
name=$(sanitize_name "$branch")
local worktrees_dir="$REPO_ROOT/.worktrees"
local wt_path="$worktrees_dir/$name"
# Check if worktree already exists
if [ -d "$wt_path" ]; then
die "Worktree '$name' already exists at $wt_path"
fi
mkdir -p "$worktrees_dir"
# Determine base port
if [ -z "$base_port" ]; then
base_port=$(find_next_base_port)
fi
calc_ports "$base_port"
info "Creating worktree '${BOLD}$name${NC}' from branch '${BOLD}$branch${NC}'"
info "Base port: $base_port"
# Check if branch exists (local or remote)
if git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
info "Using existing local branch '$branch'"
git -C "$REPO_ROOT" worktree add "$wt_path" "$branch"
elif git -C "$REPO_ROOT" show-ref --verify --quiet "refs/remotes/origin/$branch" 2>/dev/null; then
info "Creating local branch from 'origin/$branch'"
git -C "$REPO_ROOT" worktree add "$wt_path" -b "$branch" "origin/$branch"
else
info "Creating new branch '$branch' from current HEAD"
git -C "$REPO_ROOT" worktree add "$wt_path" -b "$branch"
fi
# Resolve the main repo's .git directory
local git_dir
git_dir=$(resolve_git_dir "$REPO_ROOT")
local project_name="myproject-$name"
# Generate .env.worktree
cat > "$wt_path/.env.worktree" <<EOF
# Generated by bin/worktree - do not edit manually
# Worktree: $name
# Branch: $branch
# Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
COMPOSE_PROJECT_NAME=$project_name
GIT_DIR=$git_dir
# Ports
NGINX_PORT=$NGINX_PORT
API_PORT=$API_PORT
WEB_PORT=$WEB_PORT
CHAT_PORT=$CHAT_PORT
POSTGRES_PORT=$POSTGRES_PORT
REDIS_PORT=$REDIS_PORT
DEBUG_API_PORT=$DEBUG_API_PORT
DEBUG_SIDEKIQ_PORT=$DEBUG_SIDEKIQ_PORT
EOF
success "Created .env.worktree"
# Copy api/.env if it exists
if [ -f "$REPO_ROOT/api/.env" ]; then
cp "$REPO_ROOT/api/.env" "$wt_path/api/.env"
success "Copied api/.env"
fi
# Copy and adapt chat/.env if it exists
if [ -f "$REPO_ROOT/chat/.env" ]; then
sed "s|localhost:3001|localhost:$NGINX_PORT|g" "$REPO_ROOT/chat/.env" > "$wt_path/chat/.env"
success "Copied and adapted chat/.env (port: $NGINX_PORT)"
fi
echo ""
echo -e "${BOLD}════════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}${BOLD} Worktree '$name' created successfully!${NC}"
echo -e "${BOLD}════════════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${BOLD}Path:${NC} $wt_path"
echo -e "${BOLD}Branch:${NC} $branch"
echo -e "${BOLD}Project:${NC} $project_name"
echo ""
echo -e "${BOLD}URLs:${NC}"
echo -e " API: ${CYAN}http://localhost:$NGINX_PORT${NC}"
echo -e " Web: ${CYAN}http://localhost:$NGINX_PORT/next${NC}"
echo -e " Chat: ${CYAN}http://localhost:$NGINX_PORT/chat${NC}"
echo ""
echo -e "${BOLD}To start:${NC}"
echo -e " cd $wt_path"
echo -e " docker compose --env-file .env.worktree up -d"
echo -e " ${YELLOW}# or: make up (auto-detects .env.worktree)${NC}"
echo ""
echo -e "${BOLD}To setup database:${NC}"
echo -e " docker compose --env-file .env.worktree exec api bundle exec rails db:schema:load db:seed"
echo ""
}
cmd_list() {
echo -e "${BOLD}Git Worktrees:${NC}"
echo ""
local format=" %-25s %-20s %-10s %-8s %s\n"
printf "$format" "NAME" "BRANCH" "NGINX" "DOCKER" "PATH"
printf "$format" "────────────────────────" "───────────────────" "─────────" "───────" "────────────────────"
while IFS= read -r line; do
local wt_path wt_branch wt_bare
wt_path=$(echo "$line" | awk '{print $1}')
wt_branch=$(echo "$line" | awk '{print $3}' | tr -d '[]')
[ -z "$wt_path" ] && continue
local wt_name
wt_name=$(basename "$wt_path")
local nginx_port="-"
local docker_status="-"
# Read .env.worktree if it exists
if [ -f "$wt_path/.env.worktree" ]; then
nginx_port=$(grep "^NGINX_PORT=" "$wt_path/.env.worktree" 2>/dev/null | cut -d= -f2)
local project
project=$(grep "^COMPOSE_PROJECT_NAME=" "$wt_path/.env.worktree" 2>/dev/null | cut -d= -f2)
if [ -n "$project" ]; then
local running
running=$(docker ps --filter "name=${project}-" --format '{{.Names}}' 2>/dev/null | wc -l | tr -d ' ')
if [ "$running" -gt 0 ]; then
docker_status="${GREEN}up ($running)${NC}"
else
docker_status="${YELLOW}down${NC}"
fi
fi
elif [ "$wt_path" = "$REPO_ROOT" ]; then
nginx_port="3001"
local running
running=$(docker ps --filter "name=myproject-" --format '{{.Names}}' 2>/dev/null | wc -l | tr -d ' ')
if [ "$running" -gt 0 ]; then
docker_status="${GREEN}up ($running)${NC}"
else
docker_status="${YELLOW}down${NC}"
fi
fi
printf " %-25s %-20s %-10s " "$wt_name" "$wt_branch" "$nginx_port"
echo -e "$docker_status\t$wt_path"
done < <(git -C "$REPO_ROOT" worktree list)
echo ""
}
cmd_remove() {
local name=""
local force=false
while [[ $# -gt 0 ]]; do
case "$1" in
--force|-f)
force=true
shift
;;
-*)
die "Unknown option: $1"
;;
*)
name="$1"
shift
;;
esac
done
[ -z "$name" ] && die "Worktree name required. Usage: bin/worktree remove <name> [--force]"
local wt_path
wt_path=$(get_worktree_path "$name") || die "Worktree '$name' not found"
# Don't allow removing the main worktree
if [ "$wt_path" = "$REPO_ROOT" ]; then
die "Cannot remove the main worktree"
fi
info "Removing worktree '${BOLD}$name${NC}' at $wt_path"
# Stop Docker containers if running
if [ -f "$wt_path/.env.worktree" ]; then
local project
project=$(grep "^COMPOSE_PROJECT_NAME=" "$wt_path/.env.worktree" 2>/dev/null | cut -d= -f2)
if [ -n "$project" ]; then
local running
running=$(docker ps --filter "name=${project}-" --format '{{.Names}}' 2>/dev/null | wc -l | tr -d ' ')
if [ "$running" -gt 0 ]; then
info "Stopping Docker containers for project '$project'..."
(cd "$wt_path" && docker compose --env-file .env.worktree down -v 2>/dev/null) || true
success "Containers stopped and volumes removed"
else
# Remove volumes even if containers are down
info "Cleaning up Docker volumes for project '$project'..."
(cd "$wt_path" && docker compose --env-file .env.worktree down -v 2>/dev/null) || true
fi
fi
fi
# Remove git worktree
if $force; then
git -C "$REPO_ROOT" worktree remove --force "$wt_path" 2>/dev/null || true
else
git -C "$REPO_ROOT" worktree remove "$wt_path" 2>/dev/null || {
warn "Worktree has uncommitted changes. Use --force to remove anyway."
exit 1
}
fi
success "Worktree '$name' removed"
}
cmd_status() {
local name="${1:-}"
if [ -n "$name" ]; then
local wt_path
wt_path=$(get_worktree_path "$name") || die "Worktree '$name' not found"
_show_status "$wt_path" "$name"
else
# Show status of current directory if it's a worktree
if [ -f "$PWD/.env.worktree" ]; then
_show_status "$PWD" "$(basename "$PWD")"
else
cmd_list
fi
fi
}
_show_status() {
local wt_path="$1"
local name="$2"
echo -e "${BOLD}Worktree: $name${NC}"
echo -e "${BOLD}Path:${NC} $wt_path"
# Branch info
local branch
branch=$(git -C "$wt_path" branch --show-current 2>/dev/null || echo "unknown")
echo -e "${BOLD}Branch:${NC} $branch"
if [ -f "$wt_path/.env.worktree" ]; then
echo ""
echo -e "${BOLD}Configuration (.env.worktree):${NC}"
while IFS='=' read -r key value; do
[[ "$key" =~ ^#.* ]] && continue
[ -z "$key" ] && continue
printf " %-22s %s\n" "$key" "$value"
done < "$wt_path/.env.worktree"
local project
project=$(grep "^COMPOSE_PROJECT_NAME=" "$wt_path/.env.worktree" 2>/dev/null | cut -d= -f2)
if [ -n "$project" ]; then
echo ""
echo -e "${BOLD}Docker containers:${NC}"
docker ps --filter "name=${project}-" --format ' {{.Names}}\t{{.Status}}\t{{.Ports}}' 2>/dev/null || echo " (none running)"
fi
local nginx_port
nginx_port=$(grep "^NGINX_PORT=" "$wt_path/.env.worktree" 2>/dev/null | cut -d= -f2)
if [ -n "$nginx_port" ]; then
echo ""
echo -e "${BOLD}URLs:${NC}"
echo -e " API: ${CYAN}http://localhost:$nginx_port${NC}"
echo -e " Web: ${CYAN}http://localhost:$nginx_port/next${NC}"
echo -e " Chat: ${CYAN}http://localhost:$nginx_port/chat${NC}"
fi
else
echo -e " ${YELLOW}No .env.worktree found (main worktree)${NC}"
fi
echo ""
}
cmd_start() {
local name="${1:-}"
local wt_path
if [ -n "$name" ]; then
wt_path=$(get_worktree_path "$name") || die "Worktree '$name' not found"
elif [ -f "$PWD/.env.worktree" ]; then
wt_path="$PWD"
name=$(basename "$PWD")
else
die "No worktree name specified and not inside a worktree directory"
fi
[ -f "$wt_path/.env.worktree" ] || die "No .env.worktree found in $wt_path"
info "Starting Docker containers for worktree '${BOLD}$name${NC}'..."
(cd "$wt_path" && docker compose --env-file .env.worktree up -d)
success "Containers started"
local nginx_port
nginx_port=$(grep "^NGINX_PORT=" "$wt_path/.env.worktree" | cut -d= -f2)
echo ""
echo -e "${BOLD}Access URLs:${NC}"
echo -e " API: ${CYAN}http://localhost:$nginx_port${NC}"
echo -e " Web: ${CYAN}http://localhost:$nginx_port/next${NC}"
echo -e " Chat: ${CYAN}http://localhost:$nginx_port/chat${NC}"
}
cmd_stop() {
local name="${1:-}"
local wt_path
if [ -n "$name" ]; then
wt_path=$(get_worktree_path "$name") || die "Worktree '$name' not found"
elif [ -f "$PWD/.env.worktree" ]; then
wt_path="$PWD"
name=$(basename "$PWD")
else
die "No worktree name specified and not inside a worktree directory"
fi
[ -f "$wt_path/.env.worktree" ] || die "No .env.worktree found in $wt_path"
info "Stopping Docker containers for worktree '${BOLD}$name${NC}'..."
(cd "$wt_path" && docker compose --env-file .env.worktree down)
success "Containers stopped"
}
# ────────────────────────────────────────────────────────────────────
# Main
# ────────────────────────────────────────────────────────────────────
main() {
local cmd="${1:-}"
shift 2>/dev/null || true
case "$cmd" in
create) cmd_create "$@" ;;
list|ls) cmd_list ;;
remove|rm) cmd_remove "$@" ;;
status) cmd_status "$@" ;;
start) cmd_start "$@" ;;
stop) cmd_stop "$@" ;;
help|-h|--help) usage ;;
"") usage; exit 1 ;;
*) die "Unknown command: $cmd. Run 'bin/worktree help' for usage." ;;
esac
}
main "$@"
name description argument-hint
worktree
Manage git worktrees for parallel development with isolated Docker environments. Use when the user wants to create, start, stop, remove, or check status of worktrees.
create|start|stop|remove|status

Worktree Manager

Manage git worktrees for parallel development with isolated Docker environments.

Instructions

When the user invokes this skill, follow these steps:

1. Show current state

Run bin/worktree list to show existing worktrees with their ports and Docker status.

2. Ask what to do

Ask the user what they want to do:

  • Create a new worktree
  • Start/Stop containers for a worktree
  • Remove a worktree
  • Show status of a worktree

3. For "Create"

  1. Show recent branches to help choose:
    git branch --sort=-committerdate --format='%(refname:short) (%(committerdate:relative))' | head -15
    git branch -r --sort=-committerdate --format='%(refname:short) (%(committerdate:relative))' | head -10
  2. Ask the user which branch to use (existing or new)
  3. Run bin/worktree create <branch>
  4. Show the output with access URLs
  5. Ask if they want to start Docker containers now

4. For "Start"

Run bin/worktree start <name> and display the access URLs.

5. For "Stop"

Run bin/worktree stop <name>.

6. For "Remove"

  1. Run bin/worktree status <name> to check if containers are running
  2. Warn the user about data loss (database, uncommitted changes)
  3. Run bin/worktree remove <name> (add --force if user confirms)

7. For "Status"

Run bin/worktree status <name> to show detailed information.

Important notes

  • The main worktree (repo root) always uses port 3001 by default
  • Secondary worktrees get ports starting at 4001, 5001, etc.
  • Each worktree runs its own PostgreSQL container (fully isolated)
  • After creating a worktree, the database needs to be set up:
    cd <worktree-path>
    docker compose --env-file .env.worktree exec api bundle exec rails db:schema:load db:seed
    
  • All make commands automatically detect .env.worktree in the current directory

Git Worktrees - Parallel Development

Overview

Worktrees allow working on multiple branches simultaneously, each with its own isolated Docker environment (containers, ports, database).

Primary use case: run multiple Claude Code instances in parallel on different branches, without port or data conflicts.

Prerequisites

  • Git 2.15+
  • Docker & Docker Compose
  • The main repo cloned (not a worktree)

Quickstart

# Create a worktree on a branch
bin/worktree create feature/auth

# Navigate to the worktree
cd .worktrees/feature-auth

# Start the containers
make up

# Set up the database
docker compose --env-file .env.worktree exec api bundle exec rails db:schema:load db:seed

# Work as usual...
make console
make test spec/models/user_spec.rb

# When done
make down
cd ../..
bin/worktree remove feature-auth

Commands

bin/worktree create <branch> [--base-port N]

Creates a new worktree with a fully configured Docker environment.

# From an existing local branch
bin/worktree create feature/auth

# From a remote branch
bin/worktree create feature/payments

# New branch (created from HEAD)
bin/worktree create feature/new-thing

# With a specific base port
bin/worktree create feature/auth --base-port 6000

The script:

  1. Creates the git worktree in .worktrees/<name>/
  2. Calculates available ports
  3. Generates .env.worktree with the configuration
  4. Copies api/.env
  5. Copies and adapts chat/.env (nginx port)

bin/worktree list

Displays all worktrees with their ports and Docker status.

NAME                      BRANCH               NGINX      DOCKER   PATH
myproject                 develop              3001       up (7)   /path/to/myproject
feature-auth              feature/auth         4001       up (7)   /path/to/myproject/.worktrees/feature-auth
feature-payments          feature/payments     5001       down     /path/to/myproject/.worktrees/feature-payments

bin/worktree start [name]

Starts Docker containers for a worktree.

# By name
bin/worktree start feature-auth

# From the worktree directory
cd .worktrees/feature-auth
bin/worktree start

bin/worktree stop [name]

Stops Docker containers for a worktree.

bin/worktree stop feature-auth

bin/worktree status [name]

Displays detailed status: configuration, containers, URLs.

bin/worktree status feature-auth

bin/worktree remove <name> [--force]

Removes a worktree and cleans up everything (containers, volumes, directory).

# Normal removal (fails if there are uncommitted changes)
bin/worktree remove feature-auth

# Force removal
bin/worktree remove feature-auth --force

Port Allocation

Each worktree receives a port block based on a 1000 increment (base ports 5000 and 7000 are skipped as they are reserved by macOS):

Service main (default) Worktree 1 (base 4000) Worktree 2 (base 6000)
Nginx 3001 4001 6001
API (Rails) 3000 4000 6000
Web (Vite) 5173 6173 8173
Chat 8080 9080 11080
PostgreSQL 5432 6432 8432
Redis 6379 7379 9379
Debug API 38697 39697 41697
Debug Sidekiq 38698 39698 41698

Access URLs

Application main Worktree (port 4001)
API http://localhost:3001 http://localhost:4001
Web http://localhost:3001/next http://localhost:4001/next
Chat http://localhost:3001/chat http://localhost:4001/chat

Makefile

All make commands work automatically in a worktree thanks to .env.worktree auto-detection:

cd .worktrees/feature-auth
make up          # Detects .env.worktree and uses it
make console     # Opens the Rails console in the correct container
make test spec/models/user_spec.rb
make down

Dedicated worktree targets:

make worktree-create BRANCH=feature/auth
make worktree-list
make worktree-remove NAME=feature-auth

Database

Each worktree runs its own PostgreSQL container with its own volume, so databases are fully isolated at the container level. They all use the same myproject_development database name internally.

After creating the worktree, you need to initialize the database:

cd .worktrees/feature-auth
docker compose --env-file .env.worktree exec api bundle exec rails db:schema:load db:seed

.env.worktree File

Automatically generated by bin/worktree create, this file contains the full Docker configuration for the worktree:

COMPOSE_PROJECT_NAME=myproject-feature-auth
GIT_DIR=/path/to/myproject/.git

NGINX_PORT=4001
API_PORT=4000
WEB_PORT=6173
CHAT_PORT=9080
POSTGRES_PORT=6432
REDIS_PORT=7379
DEBUG_API_PORT=39697
DEBUG_SIDEKIQ_PORT=39698

This file is in .gitignore and should not be committed.

Claude Code Skill

The /worktree skill is available in Claude Code for interactive worktree management. It suggests recent branches and guides creation/removal.

Troubleshooting

Ports already in use

Check listening ports:

lsof -i :4001

Force a specific base port:

bin/worktree create feature/auth --base-port 7000

Database does not exist

After make up, create the database:

docker compose --env-file .env.worktree exec api bundle exec rails db:schema:load db:seed

Worktree cannot see commits from the main repo

The worktree's .git points to the main repo via GIT_DIR in .env.worktree. If the main repo is moved, the worktree must be recreated.

"container name already in use" error

Another worktree is using the same ports/names. Check with:

bin/worktree list
docker ps --filter "name=myproject"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment