Skip to content

Instantly share code, notes, and snippets.

@cniska
Last active July 18, 2026 06:17
Show Gist options
  • Select an option

  • Save cniska/e9713720c1cbd82456e37e32be110a85 to your computer and use it in GitHub Desktop.

Select an option

Save cniska/e9713720c1cbd82456e37e32be110a85 to your computer and use it in GitHub Desktop.
Git Worktree Script
#!/usr/bin/env bash
# wt — one task, one worktree, one tmux window, one agent.
#
# Convention: task worktrees live at <repo>/.claude/worktrees/<branch>, the same
# path Claude Code's EnterWorktree and a manual `git worktree add` use. Set
# WT_AGENT to launch something other than `claude`.
#
# On fresh creation, wt runs an optional repo hook — scripts/worktree-setup.sh —
# so a new worktree arrives with dependencies installed. The hook is opt-in per
# repo (skipped if absent) and non-fatal (a failed install still leaves a usable
# worktree). Reusing an existing worktree skips the hook.
set -euo pipefail
agent=${WT_AGENT:-claude}
die() { printf 'wt: %s\n' "$*" >&2; exit 1; }
usage() {
cat <<'EOF'
wt — parallel-task worktrees for the orchestrator + tmux loop.
Usage:
wt <branch> create (or reuse) a worktree + tmux window, launch the agent
wt ls list this repo's task worktrees
wt path <branch> print the worktree path (for `cd "$(wt path x)"`)
wt rm <branch> remove the worktree and kill its tmux window
wt prune prune stale worktree admin entries
Worktrees live at <repo>/.claude/worktrees/<branch>. On creation, wt runs the
repo's scripts/worktree-setup.sh if present (dependency install, etc.). Override
the agent with WT_AGENT (e.g. WT_AGENT=acolyte wt task-a).
EOF
}
# Main working tree, even when invoked from inside a worktree. --git-common-dir
# resolves to the shared .git; its parent is the primary checkout.
repo_root() {
local common
common=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) \
|| die "not inside a git repository"
dirname "$common"
}
# Run the repo's worktree bootstrap so a fresh checkout is ready for the agent.
# Opt-in (only if the tracked hook exists) and non-fatal (a failed install still
# leaves a worktree to debug in), so wt stays generic across repos.
run_bootstrap() {
local path=$1 hook="$path/scripts/worktree-setup.sh"
[ -x "$hook" ] || return 0
echo "wt: bootstrapping worktree via scripts/worktree-setup.sh"
local rc=0
( cd "$path" && "$hook" ) || rc=$?
if [ "$rc" -eq 0 ]; then
echo "wt: bootstrap complete"
else
echo "wt: bootstrap failed (exit $rc) — worktree created; fix and re-run the hook" >&2
fi
}
cmd_open() {
local branch=$1
[ -n "$branch" ] || die "branch name required"
local root path created=false
root=$(repo_root)
path="$root/.claude/worktrees/$branch"
if [ -d "$path" ]; then
echo "wt: reusing existing worktree $path"
elif git -C "$root" show-ref --verify --quiet "refs/heads/$branch"; then
git -C "$root" worktree add "$path" "$branch"
created=true
else
git -C "$root" worktree add "$path" -b "$branch"
created=true
fi
[ "$created" = true ] && run_bootstrap "$path"
if [ -z "${TMUX:-}" ]; then
echo "wt: not in a tmux session — worktree ready at:"
echo " $path"
echo " cd into it and run '$agent', or start tmux first."
return
fi
# Reuse the window if it already exists, else open one and launch the agent.
if tmux list-windows -F '#W' | grep -qxF "$branch"; then
tmux select-window -t "$branch"
else
tmux new-window -c "$path" -n "$branch"
tmux send-keys -t "$branch" "$agent" C-m
fi
}
cmd_ls() {
local root path
root=$(repo_root)
path="$root/.claude/worktrees"
[ -d "$path" ] || { echo "wt: no task worktrees"; return; }
git -C "$root" worktree list --porcelain \
| awk -v p="$path/" '
/^worktree / { wt = substr($0, 10) }
/^branch / { br = substr($0, 8); sub("refs/heads/", "", br)
if (index(wt, p) == 1) printf " %-24s %s\n", br, wt }'
}
cmd_path() {
local branch=$1
[ -n "$branch" ] || die "branch name required"
echo "$(repo_root)/.claude/worktrees/$branch"
}
cmd_rm() {
local branch=$1
[ -n "$branch" ] || die "branch name required"
local root path
root=$(repo_root)
path="$root/.claude/worktrees/$branch"
[ -d "$path" ] || die "no worktree at $path"
git -C "$root" worktree remove "$path"
echo "wt: removed worktree $path"
if [ -n "${TMUX:-}" ] && tmux list-windows -F '#W' | grep -qxF "$branch"; then
tmux kill-window -t "$branch"
echo "wt: killed tmux window $branch"
fi
echo "wt: branch '$branch' kept — delete with 'git -C $root branch -d $branch' once merged"
}
cmd_prune() {
git -C "$(repo_root)" worktree prune
echo "wt: pruned stale worktree entries"
}
case "${1:-}" in
"" | -h | --help | help) usage ;;
ls) cmd_ls ;;
path) shift; cmd_path "${1:-}" ;;
rm) shift; cmd_rm "${1:-}" ;;
prune) cmd_prune ;;
*) cmd_open "$1" ;;
esac
#!/usr/bin/env bash
# Behavior tests for wt. Pure bash plus git; every case uses a throwaway repo.
# Run: /usr/local/bin/wt.test.sh (exit 0 = all pass, 1 = failures)
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
WT="$HERE/wt"
pass=0; fail=0
assert(){ # desc, got, want
if [ "$2" = "$3" ]; then pass=$((pass+1))
else fail=$((fail+1)); printf 'FAIL %s\n want: [%s]\n got: [%s]\n' "$1" "$3" "$2"; fi
}
contains(){ # desc, haystack, needle
case "$2" in *"$3"*) pass=$((pass+1)) ;;
*) fail=$((fail+1)); printf 'FAIL %s\n expected to contain: [%s]\n in: [%s]\n' "$1" "$3" "$2" ;; esac
}
TMP="$(cd "$(mktemp -d)" && pwd -P)"; REPO="$TMP/repo"
trap 'rm -rf "$TMP"' EXIT
git init -q -b main "$REPO"
( cd "$REPO"
git config user.email test@example.com
git config user.name test
printf initial > README.md
git add README.md
git commit -qm initial )
run(){ ( cd "$REPO" && env -u TMUX WT_AGENT=true "$WT" "$@" ); }
# path and ls: no worktree yet
assert "path prints the conventional location" \
"$(run path task-a)" "$REPO/.claude/worktrees/task-a"
assert "ls reports no task worktrees before creation" "$(run ls)" "wt: no task worktrees"
# open: creates a branch/worktree, runs the optional hook, and does not require tmux.
mkdir -p "$REPO/scripts"
printf '%s\n' '#!/usr/bin/env bash' 'printf ready > .wt-bootstrap-ran' > "$REPO/scripts/worktree-setup.sh"
chmod +x "$REPO/scripts/worktree-setup.sh"
( cd "$REPO" && git add scripts/worktree-setup.sh && git commit -qm bootstrap )
opened=$(run task-a)
contains "open reports bootstrap" "$opened" "wt: bootstrapping worktree via scripts/worktree-setup.sh"
contains "open reports a non-tmux checkout" "$opened" "wt: not in a tmux session"
assert "open creates the worktree" "$([ -d "$REPO/.claude/worktrees/task-a" ] && echo yes || echo no)" yes
assert "open runs the bootstrap hook only on creation" "$(cat "$REPO/.claude/worktrees/task-a/.wt-bootstrap-ran")" ready
assert "open creates the requested branch" "$(git -C "$REPO" branch --show-current; git -C "$REPO/.claude/worktrees/task-a" branch --show-current | tail -1)" $'main\ntask-a'
# reuse: must preserve the worktree and skip bootstrap.
rm "$REPO/.claude/worktrees/task-a/.wt-bootstrap-ran"
reused=$(run task-a)
contains "existing worktree is reused" "$reused" "wt: reusing existing worktree"
assert "reuse skips bootstrap" "$([ -e "$REPO/.claude/worktrees/task-a/.wt-bootstrap-ran" ] && echo yes || echo no)" no
# ls and rm: only managed worktrees appear, rm keeps the branch for post-merge cleanup.
contains "ls includes the managed branch" "$(run ls)" "task-a"
contains "ls includes the managed path" "$(run ls)" "$REPO/.claude/worktrees/task-a"
removed=$(run rm task-a)
contains "rm reports removal" "$removed" "wt: removed worktree $REPO/.claude/worktrees/task-a"
assert "rm removes the worktree" "$([ -d "$REPO/.claude/worktrees/task-a" ] && echo yes || echo no)" no
assert "rm keeps the branch" "$(git -C "$REPO" show-ref --verify --quiet refs/heads/task-a; echo $?)" 0
# errors: missing arguments and calls outside a repository stay actionable.
set +e
missing=$(run path 2>&1); missing_rc=$?
outside=$(cd "$TMP" && "$WT" ls 2>&1); outside_rc=$?
set -e
assert "path without a branch exits non-zero" "$missing_rc" 1
assert "path without a branch explains the problem" "$missing" "wt: branch name required"
assert "outside a repo exits non-zero" "$outside_rc" 1
assert "outside a repo explains the problem" "$outside" "wt: not inside a git repository"
total=$((pass + fail))
printf '\n%d/%d passed' "$pass" "$total"
[ "$fail" -eq 0 ] && { printf ' — all green\n'; exit 0; } || { printf ' — %d FAILED\n' "$fail"; exit 1; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment