Created
February 11, 2026 23:12
-
-
Save craigsc/46979018ee28788602d3452fb08ef814 to your computer and use it in GitHub Desktop.
Claude Tree Helper Commands
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 | |
| # 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=~/Projects/pastmaps | |
| # Files/directories to symlink from the main repo into each new worktree. | |
| # Remove or add entries to match your project: | |
| CT_SYMLINKS=(node_modules .dev.vars) | |
| # 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 feature-name OR ct feature name with spaces | |
| ct() { | |
| if [ -z "$1" ]; then | |
| echo "Usage: ct <name> (spaces are auto-hyphenated)" | |
| return 1 | |
| fi | |
| local feature="${(j:-:)@}" | |
| local worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${feature}" | |
| 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 | |
| echo "Worktree ready: $worktree_dir" | |
| [ -n "$CT_AUTO_COMMAND" ] && eval "$CT_AUTO_COMMAND" | |
| } | |
| # 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 worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${feature}" | |
| 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" | |
| feature="${worktree_dir:t}" # basename | |
| feature="${feature#${_ct_repo_name}-}" # strip repo prefix | |
| cd "$CT_REPO_ROOT" | |
| else | |
| echo "Usage: ctrm <name> (or run with no args from inside a worktree)" | |
| return 1 | |
| fi | |
| else | |
| feature="${(j:-:)@}" | |
| worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${feature}" | |
| 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 | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hey this is awesome! you may want to scope out https://github.com/craigsc/cmux too - I just spent some time iterating on the base scripts here and rolling them into a single worktree manager
it's quite a bit more robust and auto-generated per-project setup hooks that would have made this work out of the box with pnpm
i'd gladly accept PRs on it as well if you find this useful for your own work!