Skip to content

Instantly share code, notes, and snippets.

@aviflombaum
Created July 26, 2025 21:13
Show Gist options
  • Save aviflombaum/b89a7e4c415f3c17d3ef37476fd477f2 to your computer and use it in GitHub Desktop.
Save aviflombaum/b89a7e4c415f3c17d3ef37476fd477f2 to your computer and use it in GitHub Desktop.
Better Worktree Flow for AI CLI Agents

Git Worktree Development Flow

The Problem

When using git worktrees for parallel development, gitignored files that are essential for development (like .claude/, .env, database configs, etc.) are not present in the new worktree. This breaks development workflows that depend on these files.

The Solution

A git alias that wraps git worktree add to automatically copy essential gitignored files to new worktrees.

Setup

1. Create the Essentials File

First, create a .gitignore.essentials file in your project root that lists which gitignored items should be copied to new worktrees:

# .gitignore.essentials
# This file lists gitignored items that should be copied to new worktrees
# Format: source:destination:type
# - source: path relative to project root
# - destination: path in new worktree (optional, defaults to source)
# - type: copy|link|mkdir (optional, defaults to copy)

# Claude configuration (symlink to share between worktrees)
.claude/::.claude/:link

# AI documentation (copy for independent editing)
.ai/::.ai/:copy

# Environment files (copy and customize per worktree)
.env.development
.env.test

# Database config (copy for different DBs per worktree)
config/database.yml

# Create empty directories
tmp/:mkdir
log/:mkdir
coverage/:mkdir

# Dummy app databases (copy)
spec/dummy/db/development.sqlite3
spec/dummy/db/test.sqlite3

2. Create the Copy Script

Create a script at ~/.git-scripts/copy-worktree-essentials.sh:

#!/bin/bash

# copy-worktree-essentials.sh
# Copies essential gitignored files to a new worktree

set -e

# Check if we have the right number of arguments
if [ $# -lt 1 ]; then
    echo "Usage: $0 <worktree-path> [source-path]"
    exit 1
fi

WORKTREE_PATH="$1"
SOURCE_PATH="${2:-$(pwd)}"

# Resolve absolute paths
WORKTREE_PATH=$(cd "$WORKTREE_PATH" 2>/dev/null && pwd)
SOURCE_PATH=$(cd "$SOURCE_PATH" 2>/dev/null && pwd)

if [ ! -d "$WORKTREE_PATH" ]; then
    echo "Error: Worktree path does not exist: $WORKTREE_PATH"
    exit 1
fi

# Look for .gitignore.essentials file
ESSENTIALS_FILE="$SOURCE_PATH/.gitignore.essentials"
if [ ! -f "$ESSENTIALS_FILE" ]; then
    echo "No .gitignore.essentials file found in $SOURCE_PATH"
    echo "Skipping essentials copy."
    exit 0
fi

echo "Copying essentials from $SOURCE_PATH to $WORKTREE_PATH"

# Process each line in the essentials file
while IFS= read -r line || [ -n "$line" ]; do
    # Skip comments and empty lines
    [[ "$line" =~ ^[[:space:]]*# ]] && continue
    [[ -z "${line// }" ]] && continue
    
    # Parse the line (format: source:destination:type)
    IFS=':' read -r source dest type <<< "$line"
    
    # Trim whitespace
    source=$(echo "$source" | xargs)
    dest=$(echo "${dest:-$source}" | xargs)
    type=$(echo "${type:-copy}" | xargs)
    
    # Skip if source is empty
    [ -z "$source" ] && continue
    
    SOURCE_FILE="$SOURCE_PATH/$source"
    DEST_FILE="$WORKTREE_PATH/$dest"
    
    case "$type" in
        "link"|"symlink")
            if [ -e "$SOURCE_FILE" ]; then
                echo "  Linking: $source -> $dest"
                # Remove existing file/link if it exists
                [ -e "$DEST_FILE" ] && rm -rf "$DEST_FILE"
                # Create parent directory if needed
                mkdir -p "$(dirname "$DEST_FILE")"
                # Create symlink with absolute path
                ln -s "$SOURCE_FILE" "$DEST_FILE"
            else
                echo "  Warning: Source not found for link: $source"
            fi
            ;;
            
        "mkdir"|"directory")
            echo "  Creating directory: $dest"
            mkdir -p "$DEST_FILE"
            ;;
            
        "copy"|*)
            if [ -e "$SOURCE_FILE" ]; then
                echo "  Copying: $source -> $dest"
                # Create parent directory if needed
                mkdir -p "$(dirname "$DEST_FILE")"
                # Copy preserving permissions and timestamps
                if [ -d "$SOURCE_FILE" ]; then
                    cp -rp "$SOURCE_FILE" "$DEST_FILE"
                else
                    cp -p "$SOURCE_FILE" "$DEST_FILE"
                fi
            else
                echo "  Warning: Source not found: $source"
            fi
            ;;
    esac
done < "$ESSENTIALS_FILE"

echo "Essentials copy complete!"

Make the script executable:

chmod +x ~/.git-scripts/copy-worktree-essentials.sh

3. Create the Git Alias

Add this to your ~/.gitconfig:

[alias]
    # Create worktree with essentials
    wt-add = "!f() { \
        if [ $# -lt 1 ]; then \
            echo 'Usage: git wt-add <path> [<branch>]'; \
            return 1; \
        fi; \
        git worktree add \"$@\" && \
        ~/.git-scripts/copy-worktree-essentials.sh \"$1\" \"$(git rev-parse --show-toplevel)\"; \
    }; f"
    
    # List worktrees with status
    wt-list = "worktree list --porcelain"
    
    # Remove worktree and clean up
    wt-remove = "!f() { \
        if [ $# -lt 1 ]; then \
            echo 'Usage: git wt-remove <path>'; \
            return 1; \
        fi; \
        echo 'Removing worktree: $1'; \
        git worktree remove \"$1\"; \
    }; f"

Usage

Creating a New Worktree

Instead of using git worktree add, use the new alias:

# Create a new worktree for a feature branch
git wt-add ../worktrees/feature-auth feature/authentication

# Create a worktree for a new branch based on main
git wt-add ../worktrees/bugfix-123 -b bugfix/issue-123 main

# Create a worktree with just a path (uses current branch point)
git wt-add ../worktrees/experiment

Real-World Example

cd ~/projects/prompt-engine

# Check current worktrees
git wt-list

# Create a new worktree for library integration
git wt-add ../worktrees/library-integration -b feature/library-integration main

# Output:
# Preparing worktree (new branch 'feature/library-integration')
# HEAD is now at abc123 Latest commit message
# Copying essentials from /Users/avi/projects/prompt-engine to /Users/avi/projects/worktrees/library-integration
#   Linking: .claude/ -> .claude/
#   Copying: .ai/ -> .ai/
#   Copying: .env.development -> .env.development
#   Creating directory: tmp/
#   Creating directory: log/
# Essentials copy complete!

# Now you can immediately use the new worktree
cd ../worktrees/library-integration
claude-swarm start .claude/rails-engine-dev-swarm.yml  # Works immediately!

Advanced Configuration

Per-Project Essentials

Different projects can have different .gitignore.essentials files:

Rails Project:

# .gitignore.essentials
.claude/:link
config/database.yml:copy
config/credentials.yml.enc:copy
config/master.key:copy
.env:copy
tmp/:mkdir
log/:mkdir
storage/:mkdir

Node.js Project:

# .gitignore.essentials
.claude/:link
.env:copy
.env.local:copy
node_modules/:link  # Share node_modules between worktrees
dist/:mkdir
coverage/:mkdir

Python Project:

# .gitignore.essentials
.claude/:link
.env:copy
venv/:mkdir  # Create empty, will recreate venv per worktree
.pytest_cache/:mkdir
__pycache__/:mkdir

Conditional Copying

You can extend the script to support conditional copying based on file existence:

# .gitignore.essentials
# Only copy if exists in source
.env.development:copy:if-exists
.env.production:copy:if-exists

# Always create these directories
tmp/:mkdir:always
log/:mkdir:always

Benefits

  1. Seamless Workflow - New worktrees are immediately ready for development
  2. Project-Specific - Each project defines its own essential files
  3. Flexible - Choose between copying, linking, or creating directories
  4. Version Controlled - The .gitignore.essentials file is tracked, ensuring consistency across team
  5. No Overhead - Only runs when creating worktrees, not on every git operation
  6. Safe - Doesn't affect normal git operations or existing workflows

Troubleshooting

Script Not Found

# Make sure the script is executable
chmod +x ~/.git-scripts/copy-worktree-essentials.sh

# Check the path in your git alias matches the script location
git config --get alias.wt-add

Essentials Not Copying

# Check if .gitignore.essentials exists
ls -la .gitignore.essentials

# Run the script manually to see detailed output
~/.git-scripts/copy-worktree-essentials.sh /path/to/worktree /path/to/source

Permission Issues

# Some files might need special permissions preserved
# The script uses cp -p to preserve permissions
# For sensitive files like keys, ensure proper permissions after copying
chmod 600 config/master.key

Best Practices

  1. Use Symlinks for Shared Resources - Configuration directories that should be identical across worktrees
  2. Copy Environment Files - So each worktree can have different settings
  3. Create Empty Directories - For logs, tmp files, and build outputs
  4. Document in README - Let team members know about the git wt-add command
  5. Regular Cleanup - Use git worktree prune to clean up deleted worktrees

Integration with CI/CD

You can also use this pattern in CI/CD pipelines:

# .github/workflows/test.yml
steps:
  - uses: actions/checkout@v3
  - name: Setup test worktree
    run: |
      git wt-add test-worktree
      cd test-worktree
      # Your tests have all necessary files

Conclusion

This approach solves the gitignored files problem elegantly by:

  • Extending git's worktree functionality without modifying git itself
  • Providing project-specific configuration through .gitignore.essentials
  • Maintaining compatibility with standard git workflows
  • Offering flexibility in how different types of files are handled

The abstraction is clean: "Essential development files follow me to new worktrees."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment