Skip to content

Instantly share code, notes, and snippets.

@woodward54
Forked from craigsc/ct-worktree-functions.zsh
Created February 13, 2026 22:03
Show Gist options
  • Select an option

  • Save woodward54/8cc8d011cf82dda3146aceeb0a860aa9 to your computer and use it in GitHub Desktop.

Select an option

Save woodward54/8cc8d011cf82dda3146aceeb0a860aa9 to your computer and use it in GitHub Desktop.
Claude Tree Helper Commands
# ct — Claude worktree helpers for parallel work
# https://gist.github.com/craigsc/46979018ee28788602d3452fb08ef814
#
# Lightweight zsh functions for managing git worktrees, designed for
# running multiple Claude Code sessions in parallel on the same repo.
#
# Each worktree gets its own branch and directory, with configurable
# symlinks so you don't have to reinstall dependencies every time.
#
# Functions:
# ct <name> — Create a worktree, symlink shared files, launch Claude
# ct --cursor <name> — Same as above, but open in Cursor instead of CLI Claude
# ctcd <name> — cd into an existing worktree
# ctrm [name] — Remove a worktree and its branch (no args = current worktree)
# ctls — List all worktrees
# ctiso — Replace symlinked node_modules with a real install
#
# Names can use spaces: `ct my feature` becomes branch `my-feature`
# and directory `<repo>-my-feature` alongside your main repo.
# ── Configuration ─────────────────────────────────────────────────────
# Point this at your main (non-worktree) repo checkout:
CT_REPO_ROOT=~/dev/merlin
# Files/directories to symlink from the main repo into each new worktree.
# Remove or add entries to match your project:
CT_SYMLINKS=()
# Command to run after creating a worktree (set to "" to disable):
CT_AUTO_COMMAND="claude"
# ──────────────────────────────────────────────────────────────────────
# Derived — no need to edit these
_ct_repo_name="${CT_REPO_ROOT:t}" # e.g. "pastmaps"
_ct_parent_dir="${CT_REPO_ROOT:h}" # e.g. ~/Projects
# Create a worktree with a new branch and launch Claude
# Usage: ct [--cursor] feature-name OR ct feature name with spaces
ct() {
local use_cursor=false
if [[ "$1" == "--cursor" ]]; then
use_cursor=true
shift
fi
if [ -z "$1" ]; then
echo "Usage: ct [--cursor] <name> (spaces are auto-hyphenated)"
return 1
fi
local feature="${(j:-:)@}"
local dir_name="${feature//\//-}"
local worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}"
git -C "$CT_REPO_ROOT" worktree add "$worktree_dir" -b "$feature" || return 1
cd "$worktree_dir"
# Symlink shared files/directories from the main repo
for item in $CT_SYMLINKS; do
if [ -e "$CT_REPO_ROOT/$item" ]; then
ln -sf "$CT_REPO_ROOT/$item" "$item"
fi
done
# Install dependencies
cd frontend
pnpm i
cd ..
echo "Worktree ready: $worktree_dir"
if $use_cursor; then
# Open in Cursor and run Claude Code in the integrated terminal
cursor "$worktree_dir"
elif [ -n "$CT_AUTO_COMMAND" ]; then
eval "$CT_AUTO_COMMAND"
fi
}
# cd into an existing worktree
# Usage: ctcd feature-name OR ctcd feature name with spaces
ctcd() {
if [ -z "$1" ]; then
echo "Usage: ctcd <name> (spaces are auto-hyphenated)"
return 1
fi
local feature="${(j:-:)@}"
local dir_name="${feature//\//-}"
local worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}"
if [ ! -d "$worktree_dir" ]; then
echo "Worktree not found: $worktree_dir"
echo "Run 'ctls' to see available worktrees."
return 1
fi
cd "$worktree_dir"
[ -f .dev.vars ] && source .dev.vars
}
# Remove a worktree and its branch
# Usage: ctrm feature-name OR ctrm (no args = current worktree)
ctrm() {
local feature worktree_dir
if [ -z "$1" ]; then
# No argument — remove current worktree if we're in one
if [[ "$PWD" == "$_ct_parent_dir/${_ct_repo_name}"-* ]]; then
worktree_dir="$PWD"
# Get the actual branch name from git (reliable even with / in names)
feature="$(git -C "$PWD" rev-parse --abbrev-ref HEAD)"
cd "$CT_REPO_ROOT"
else
echo "Usage: ctrm <name> (or run with no args from inside a worktree)"
return 1
fi
else
feature="${(j:-:)@}"
local dir_name="${feature//\//-}"
worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}"
fi
if [ ! -d "$worktree_dir" ]; then
echo "Worktree not found: $worktree_dir"
return 1
fi
git -C "$CT_REPO_ROOT" worktree remove "$worktree_dir" && \
git -C "$CT_REPO_ROOT" branch -d "$feature" 2>/dev/null
}
# List all worktrees (excluding the main repo)
ctls() {
git -C "$CT_REPO_ROOT" worktree list | tail -n +2
}
# Isolate node_modules — replace the symlink with a real install
# (useful when you need to modify dependencies in a worktree)
ctiso() {
if [ -L "node_modules" ]; then
echo "Removing symlink and running npm install..."
rm node_modules
npm install
else
echo "node_modules is not a symlink — already isolated."
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment