Skip to content

Instantly share code, notes, and snippets.

@lightstrike
Last active June 27, 2026 01:07
Show Gist options
  • Select an option

  • Save lightstrike/7d3a75073560a61a89cb555fa82e3bf3 to your computer and use it in GitHub Desktop.

Select an option

Save lightstrike/7d3a75073560a61a89cb555fa82e3bf3 to your computer and use it in GitHub Desktop.
worktree agents: git worktree launchers for Codex, Agent, Agy, and Pi

worktree agents

POSIX /bin/sh wrappers that create or reuse a git worktree, cd into it, and launch an AI coding CLI there — so parallel agent sessions stay isolated on separate branches without touching your main checkout.

Why

Running multiple coding agents in one repo gets messy fast: dirty trees, branch thrash, and agents stepping on each other. These scripts give each session its own worktree + branch under a predictable directory, then start the tool inside it.

Commands

Command Launches Default worktree root
wclaude Claude Code (claude) <repo>/.claude/worktrees/
wcodex Codex CLI <repo>/.codex/worktrees/
wagent Cursor Agent (agent) <repo>/.agent/worktrees/
wagy Antigravity CLI (agy) <repo>/.agy/worktrees/
wpi Pi <repo>/.pi/worktrees/

Override roots with env vars: WCLAUDE_WORKTREE_ROOT, WCODEX_WORKTREE_ROOT, WAGENT_WORKTREE_ROOT, WAGY_WORKTREE_ROOT, WPI_WORKTREE_ROOT.

Setup

  1. Pick an install directory (e.g. ~/bin). All files must live in the same directory.

  2. Download every file from this gist:

dir=~/bin
gist=7d3a75073560a61a89cb555fa82e3bf3
for f in README.md _worktree-agent.sh _wta-launch.sh wclaude wcodex wagent wagy wpi; do
  gh gist view "$gist" -f "$f" -r > "$dir/$f"
done
chmod +x "$dir"/_worktree-agent.sh "$dir"/_wta-launch.sh "$dir"/wclaude "$dir"/wcodex "$dir"/wagent "$dir"/wagy "$dir"/wpi
  1. Ensure the directory is on your PATH.

  2. Optional shell aliases (zsh/bash):

alias wclaude="$HOME/bin/wclaude"
alias wcodex="$HOME/bin/wcodex"
alias wagent="$HOME/bin/wagent"
alias wagy="$HOME/bin/wagy"
alias wpi="$HOME/bin/wpi"
  1. Confirm the CLIs themselves are installed and on PATH: claude, codex, agent, agy, pi.

Usage

# Random worktree name + branch, launch agent in yolo mode
wagent

# Named worktree/branch
wagent fix-login

# Named worktree, pass flags/prompt to the underlying CLI
wagent fix-login --model sonnet-4-thinking "investigate failing CI"

# Skip the name; forward everything after -- to the CLI
wagent -- "investigate failing CI"

# Custom branch, custom base ref
wagent --branch feat/fix-login --base staging -- "implement the plan"

# Create/reuse worktree only; print path, do not launch
wagent --no-launch fix-login

Launcher options (shared by all four):

  • -n, --name NAME — worktree directory name
  • -b, --branch BRANCH — branch to create or reuse (defaults to name)
  • --base REF — base ref for new branches (default: HEAD)
  • --root DIR — worktree root directory
  • --mode MODE — launch mode: dangerous, safe, or auto
  • --dangerous — launch with the default dangerous/yolo flags
  • --safe — launch without dangerous/yolo flags
  • --auto — launch with each tool's auto-approval mode where available
  • --no-launch — set up worktree and print path only
  • -h, --help — launcher help

Use -- when the first argument to the underlying CLI is a flag, or when passing a multi-word prompt without a worktree name.

Yolo / dangerous mode (default)

Each launcher prepends its tool's auto-approve flag unless you already passed one:

Launcher Default flag
wclaude --dangerously-skip-permissions
wcodex --dangerously-bypass-approvals-and-sandbox
wagent --force --sandbox disabled
wagy --dangerously-skip-permissions
wpi --approve

Pass --safe before the worktree name/prompt to suppress dangerous defaults, or --auto for each tool's middle auto-approval policy.

Random names

When no name is given, worktrees get names like open-orbit-f74151 — adjective + noun + hex suffix (A–Z word lists).

Worktree roots inside the repo are appended to .git/info/exclude automatically so they stay out of git status.

Files in this gist

File Role
README.md This doc
_worktree-agent.sh Shared core
_wta-launch.sh Resolves install dir and execs core
wclaude, wcodex, wagent, wagy, wpi Thin entrypoint wrappers

Requires: /bin/sh, git, and the target CLI on PATH.

#!/bin/sh
# Worktree agents: POSIX sh git worktree launchers for Claude, Codex, Agent, Agy, and Pi.
set -eu
WTA_LAUNCHER=
WTA_TOOL=
WTA_ROOT_ENV=
WTA_ROOT_DEFAULT=
tool_n=0
_wta_die() {
printf '%s\n' "${WTA_LAUNCHER}: $*" >&2
exit 1
}
_wta_print() {
printf '%s\n' "$*"
}
_wta_print_stderr() {
printf '%s\n' "$*" >&2
}
_wta_configure() {
case "$1" in
claude)
WTA_LAUNCHER=wclaude
WTA_TOOL=claude
WTA_ROOT_ENV=WCLAUDE_WORKTREE_ROOT
WTA_ROOT_DEFAULT=.claude/worktrees
;;
codex)
WTA_LAUNCHER=wcodex
WTA_TOOL=codex
WTA_ROOT_ENV=WCODEX_WORKTREE_ROOT
WTA_ROOT_DEFAULT=.codex/worktrees
;;
agent)
WTA_LAUNCHER=wagent
WTA_TOOL=agent
WTA_ROOT_ENV=WAGENT_WORKTREE_ROOT
WTA_ROOT_DEFAULT=.agent/worktrees
;;
agy)
WTA_LAUNCHER=wagy
WTA_TOOL=agy
WTA_ROOT_ENV=WAGY_WORKTREE_ROOT
WTA_ROOT_DEFAULT=.agy/worktrees
;;
pi)
WTA_LAUNCHER=wpi
WTA_TOOL=pi
WTA_ROOT_ENV=WPI_WORKTREE_ROOT
WTA_ROOT_DEFAULT=.pi/worktrees
;;
*)
printf '%s\n' "worktree-agent: unknown tool: $1" >&2
return 1
;;
esac
}
_wta_usage() {
cat <<EOF
usage:
${WTA_LAUNCHER} [name] [${WTA_TOOL} args...]
${WTA_LAUNCHER} [options] [--] [${WTA_TOOL} args...]
Create or reuse a git worktree, cd into it, then launch ${WTA_TOOL} there.
If name is omitted, ${WTA_LAUNCHER} generates a random worktree/branch name.
If the first argument is ${WTA_TOOL} option, or if you use --, the name is omitted
and all remaining arguments are forwarded to ${WTA_TOOL}.
examples:
${WTA_LAUNCHER}
${WTA_LAUNCHER} fix-login
${WTA_LAUNCHER} fix-login --model gpt-5.5 "investigate failing CI"
${WTA_LAUNCHER} --safe fix-login
${WTA_LAUNCHER} --branch feat/fix-login -- "investigate failing CI"
options:
-n, --name NAME Worktree directory name. Defaults to a random name.
-b, --branch BRANCH Branch to create/reuse. Defaults to NAME.
--base REF Base ref for new branches. Defaults to HEAD.
--root DIR Worktree root. Defaults to \$${WTA_ROOT_ENV} or
<repo>/${WTA_ROOT_DEFAULT}.
--mode MODE Launch mode: dangerous, safe, or auto.
--dangerous Launch with the default dangerous/yolo flags.
--safe Launch without dangerous/yolo flags.
--auto Launch with each tool's auto-approval mode where available.
--no-launch Create/reuse the worktree and print the path, but do
not launch ${WTA_TOOL}.
-h, --help Show this help.
By default, launches in yolo/dangerous mode when supported:
claude -> --dangerously-skip-permissions
codex -> --dangerously-bypass-approvals-and-sandbox
agent -> --force --sandbox disabled
agy -> --dangerously-skip-permissions
pi -> --approve
Use --safe or pass the tool's own safety flags before the prompt to opt out.
EOF
}
_wta_random_suffix() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 3
elif command -v uuidgen >/dev/null 2>&1; then
uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-' | cut -c1-6
else
printf '%06x\n' $(( ($(date +%s) + $$) % 16777216 ))
fi
}
_wta_pick_word() {
words=$1
# shellcheck disable=SC2086
set -- $words
count=$#
[ "$count" -gt 0 ] || return 1
idx=$(( ( ($(date +%s) + $$) % count ) + 1 ))
i=1
for word in "$@"; do
if [ "$i" -eq "$idx" ]; then
printf '%s' "$word"
return 0
fi
i=$((i + 1))
done
}
_wta_random_name() {
adjectives="active bold crisp direct eager fresh grand hardy ideal just keen lucid mild neat open plain quick rapid sharp tight upright vivid warm xenial young zesty"
nouns="arc beam cipher deck ember forge grid hub ion jet key loop matrix node orbit patch query relay signal trace unit vector wire xenon yield zone"
adjective=$(_wta_pick_word "$adjectives") || return 1
noun=$(_wta_pick_word "$nouns") || return 1
suffix=$(_wta_random_suffix) || return 1
printf '%s' "${adjective}-${noun}-${suffix}"
}
_wta_path_name_for() {
name=$1
name=$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')
name=$(printf '%s' "$name" | sed 's/\//__/g; s/[^a-z0-9._-]/-/g; s/^[-.]*//; s/[-.]*$//')
if [ -z "$name" ]; then
name=$(_wta_random_name) || return 1
fi
printf '%s' "$name"
}
_wta_is_probable_prompt() {
_wta_tab=$(printf '\t')
case "$1" in
*" "*|*"$_wta_tab"*) return 0 ;;
*) return 1 ;;
esac
}
_wta_tool_push() {
tool_n=$((tool_n + 1))
eval "tool_$tool_n=\$1"
}
_wta_tool_push_all() {
for arg in "$@"; do
_wta_tool_push "$arg"
done
}
_wta_tool_option_requires_value() {
case "$WTA_TOOL" in
claude)
case "$1" in
--add-dir|\
--allowedTools|\
--append-system-prompt|\
--disallowedTools|\
--input-format|\
--mcp-config|\
--model|\
--output-format|\
--permission-mode|\
--permission-prompt-tool|\
--settings|\
--session-id)
return 0
;;
esac
;;
codex)
case "$1" in
-a|--ask-for-approval|\
-c|--config|\
-C|--cd|\
-i|--image|\
-m|--model|\
-p|--profile|\
-s|--sandbox|\
--add-dir|\
--disable|\
--enable|\
--local-provider|\
--remote|\
--remote-auth-token-env)
return 0
;;
esac
;;
agent)
case "$1" in
--api-key|\
-H|--header|\
--output-format|\
--mode|\
--model|\
--sandbox|\
--workspace|\
--plugin-dir|\
--worktree-base)
return 0
;;
esac
;;
agy)
case "$1" in
--add-dir|\
--conversation|\
--log-file|\
--model|\
--print-timeout|\
--project|\
--prompt|\
--prompt-interactive)
return 0
;;
esac
;;
pi)
case "$1" in
--provider|\
--model|\
--api-key|\
--system-prompt|\
--append-system-prompt|\
--mode|\
--session|\
--session-id|\
--fork|\
--session-dir|\
-n|--name|\
--models|\
-t|--tools|\
-xt|--exclude-tools|\
--thinking|\
-e|--extension|\
--skill|\
--prompt-template|\
--theme|\
--export|\
--list-models)
return 0
;;
esac
;;
esac
return 1
}
_wta_abs_path() {
path=$1
if [ -d "$path" ]; then
( CDPATH='' cd -- "$path" && pwd )
else
mkdir -p "$path"
( CDPATH='' cd -- "$path" && pwd )
fi
}
_wta_resolve_root() {
repo_root=$1
root=$2
case $root in
"~") root=$HOME ;;
esac
if [ "${root#~/}" != "$root" ]; then
root=$HOME/${root#~/}
fi
if [ "${root#/}" != "$root" ]; then
:
else
root=$repo_root/$root
fi
_wta_abs_path "$root"
}
_wta_root_default() {
eval "value=\${$WTA_ROOT_ENV-}"
if [ -z "$value" ]; then
value=$WTA_ROOT_DEFAULT
fi
printf '%s' "$value"
}
_wta_ensure_excluded_if_inside_repo() {
repo_root=$1
worktree_root=$2
case $worktree_root in
"$repo_root"/*) ;;
*) return 0 ;;
esac
rel=${worktree_root#"$repo_root"/}
case $rel in
""|.git|.git/*) return 0 ;;
esac
git_dir=$(git -C "$repo_root" rev-parse --git-dir 2>/dev/null) || return 0
if [ "${git_dir#/}" != "$git_dir" ]; then
:
else
git_dir=$repo_root/$git_dir
fi
exclude_file=$git_dir/info/exclude
mkdir -p "$(dirname "$exclude_file")" || return 1
if [ -f "$exclude_file" ]; then
if grep -qxF "$rel" "$exclude_file" || grep -qxF "$rel/" "$exclude_file"; then
return 0
fi
fi
{
printf '\n'
printf '# worktree agents (%s)\n' "$WTA_LAUNCHER"
printf '%s/\n' "$rel"
} >>"$exclude_file"
}
_wta_create_or_reuse_worktree() {
repo_root=$1
worktree_path=$2
branch=$3
base_ref=$4
mkdir -p "$(dirname "$worktree_path")" || return 1
if [ -e "$worktree_path/.git" ]; then
_wta_print_stderr "${WTA_LAUNCHER}: reusing worktree: $worktree_path"
return 0
fi
if [ -e "$worktree_path" ]; then
_wta_die "target path exists but is not a git worktree: $worktree_path"
fi
if git -C "$repo_root" show-ref --verify --quiet "refs/heads/$branch"; then
_wta_print_stderr "${WTA_LAUNCHER}: adding worktree for existing branch '$branch'"
git -C "$repo_root" worktree add "$worktree_path" "$branch"
else
_wta_print_stderr "${WTA_LAUNCHER}: creating branch '$branch' from '$base_ref'"
git -C "$repo_root" worktree add -b "$branch" "$worktree_path" "$base_ref"
fi
}
_wta_tool_has_any() {
pattern=$1
shift
i=1
while [ "$i" -le "$tool_n" ]; do
eval "arg=\$tool_$i"
if [ "$arg" = "$pattern" ]; then
return 0
fi
case $arg in
"${pattern}"=*) return 0 ;;
esac
i=$((i + 1))
done
[ $# -eq 0 ] && return 1
_wta_tool_has_any "$@"
}
_wta_validate_mode() {
case "$1" in
dangerous|safe|auto) return 0 ;;
*) _wta_die "invalid launch mode: $1" ;;
esac
}
_wta_tool_exec() {
set --
case "$launch_mode:$WTA_TOOL" in
dangerous:claude)
if ! _wta_tool_has_any --dangerously-skip-permissions --permission-mode; then
set -- "$@" --dangerously-skip-permissions
fi
;;
dangerous:codex)
if ! _wta_tool_has_any --dangerously-bypass-approvals-and-sandbox --ask-for-approval -a; then
set -- "$@" --dangerously-bypass-approvals-and-sandbox
fi
;;
dangerous:agent)
if ! _wta_tool_has_any --yolo --force -f --sandbox; then
set -- "$@" --force --sandbox disabled
fi
;;
dangerous:agy)
if ! _wta_tool_has_any --dangerously-skip-permissions --sandbox; then
set -- "$@" --dangerously-skip-permissions
fi
;;
dangerous:pi)
if ! _wta_tool_has_any --approve --no-approve; then
set -- "$@" --approve
fi
;;
safe:codex)
if ! _wta_tool_has_any --dangerously-bypass-approvals-and-sandbox --ask-for-approval -a --sandbox -s; then
set -- "$@" --ask-for-approval on-request --sandbox workspace-write
fi
;;
safe:agent)
if ! _wta_tool_has_any --yolo --force -f --sandbox; then
set -- "$@" --sandbox enabled
fi
;;
safe:pi)
if ! _wta_tool_has_any --approve --no-approve; then
set -- "$@" --no-approve
fi
;;
auto:claude)
if ! _wta_tool_has_any --dangerously-skip-permissions --permission-mode; then
set -- "$@" --permission-mode auto
fi
;;
auto:codex)
if ! _wta_tool_has_any --dangerously-bypass-approvals-and-sandbox --ask-for-approval -a --sandbox -s; then
set -- "$@" --ask-for-approval never --sandbox workspace-write
fi
;;
auto:agent)
if ! _wta_tool_has_any --yolo --force -f --sandbox --auto-review; then
set -- "$@" --auto-review --sandbox enabled
fi
;;
auto:agy)
if ! _wta_tool_has_any --dangerously-skip-permissions --sandbox; then
set -- "$@" --sandbox
fi
;;
auto:pi)
if ! _wta_tool_has_any --approve --no-approve; then
set -- "$@" --approve
fi
;;
esac
i=1
while [ "$i" -le "$tool_n" ]; do
eval "arg=\$tool_$i"
set -- "$@" "$arg"
i=$((i + 1))
done
exec "$tool_bin" "$@"
}
_wta_main() {
worktree_name=
branch=
base_ref=HEAD
worktree_root_arg=
no_launch=0
launch_mode=dangerous
tool_n=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
_wta_usage
return 0
;;
-n|--name)
shift
[ $# -gt 0 ] || _wta_die "--name requires a value"
worktree_name=$1
shift
;;
--name=*)
worktree_name=${1#--name=}
shift
;;
-b|--branch)
shift
[ $# -gt 0 ] || _wta_die "--branch requires a value"
branch=$1
shift
;;
--branch=*)
branch=${1#--branch=}
shift
;;
--base)
shift
[ $# -gt 0 ] || _wta_die "--base requires a value"
base_ref=$1
shift
;;
--base=*)
base_ref=${1#--base=}
shift
;;
--root)
shift
[ $# -gt 0 ] || _wta_die "--root requires a value"
worktree_root_arg=$1
shift
;;
--root=*)
worktree_root_arg=${1#--root=}
shift
;;
--mode)
shift
[ $# -gt 0 ] || _wta_die "--mode requires a value"
_wta_validate_mode "$1"
launch_mode=$1
shift
;;
--mode=*)
launch_mode=${1#--mode=}
_wta_validate_mode "$launch_mode"
shift
;;
--dangerous)
launch_mode=dangerous
shift
;;
--safe)
launch_mode=safe
shift
;;
--auto)
launch_mode=auto
shift
;;
--no-launch)
no_launch=1
shift
;;
--)
shift
_wta_tool_push_all "$@"
break
;;
-*)
_wta_tool_push "$1"
if [ "${1#*=}" = "$1" ] && _wta_tool_option_requires_value "$1"; then
shift
[ $# -gt 0 ] || _wta_die "${WTA_TOOL} option requires a value"
_wta_tool_push "$1"
fi
shift
;;
*)
if [ -z "$worktree_name" ] && [ -z "$branch" ] && ! _wta_is_probable_prompt "$1"; then
worktree_name=$1
shift
else
_wta_tool_push_all "$@"
break
fi
;;
esac
done
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) ||
_wta_die "not inside a git repository"
if [ -z "$worktree_name" ] && [ -z "$branch" ]; then
worktree_name=$(_wta_random_name) || _wta_die "failed to generate a random name"
elif [ -z "$worktree_name" ]; then
worktree_name=$(_wta_path_name_for "$branch") || _wta_die "failed to build worktree path name"
fi
[ -n "$branch" ] || branch=$worktree_name
git -C "$repo_root" check-ref-format --branch "$branch" >/dev/null 2>&1 ||
_wta_die "invalid branch name: $branch"
if [ -n "$worktree_root_arg" ]; then
root_input=$worktree_root_arg
else
root_input=$(_wta_root_default)
fi
worktree_root=$(_wta_resolve_root "$repo_root" "$root_input") ||
_wta_die "failed to resolve worktree root"
_wta_ensure_excluded_if_inside_repo "$repo_root" "$worktree_root" ||
_wta_die "failed to add worktree root to git info exclude"
path_name=$(_wta_path_name_for "$worktree_name") || _wta_die "failed to build worktree path"
worktree_path=$worktree_root/$path_name
_wta_create_or_reuse_worktree "$repo_root" "$worktree_path" "$branch" "$base_ref" ||
_wta_die "failed to create or reuse worktree"
if [ "$no_launch" -eq 1 ]; then
_wta_print "$worktree_path"
return 0
fi
tool_bin=$(command -v "$WTA_TOOL") || _wta_die "${WTA_TOOL} executable not found in PATH"
cd "$worktree_path" || _wta_die "failed to cd into worktree: $worktree_path"
_wta_tool_exec
}
if [ "${0##*/}" = "_worktree-agent.sh" ]; then
[ $# -gt 0 ] || {
printf '%s\n' "worktree-agent: tool name required (claude, codex, agent, agy, pi)" >&2
exit 1
}
_wta_configure "$1" || exit 1
shift
_wta_main "$@"
fi
#!/bin/sh
# Shared by wclaude, wcodex, wagent, wagy, and wpi. Resolves install dir and execs core.
# Usage: _wta-launch.sh <tool> [args...]
set -eu
tool=$1
shift
script=$0
case $script in
/*|./*|../*|*/*) ;;
*)
script=$(command -v -- "$script" 2>/dev/null || printf '%s' "$script")
;;
esac
while [ -L "$script" ]; do
link=$(readlink "$script") || break
if [ "${link#/}" != "$link" ]; then
script=$link
else
script=$(dirname "$script")/$link
fi
done
dir=$(CDPATH='' cd -- "$(dirname "$script")" && pwd)
exec "$dir/_worktree-agent.sh" "$tool" "$@"
#!/bin/sh
_wrapper=$0
case $_wrapper in
/*|./*|../*|*/*) _dir=$(dirname "$_wrapper") ;;
*) _dir=$(dirname "$(command -v -- "$_wrapper")") ;;
esac
_dir=$(CDPATH='' cd -- "$_dir" && pwd)
exec "$_dir/_wta-launch.sh" agent "$@"
#!/bin/sh
_wrapper=$0
case $_wrapper in
/*|./*|../*|*/*) _dir=$(dirname "$_wrapper") ;;
*) _dir=$(dirname "$(command -v -- "$_wrapper")") ;;
esac
_dir=$(CDPATH='' cd -- "$_dir" && pwd)
exec "$_dir/_wta-launch.sh" agy "$@"
#!/bin/sh
_wrapper=$0
case $_wrapper in
/*|./*|../*|*/*) _dir=$(dirname "$_wrapper") ;;
*) _dir=$(dirname "$(command -v -- "$_wrapper")") ;;
esac
_dir=$(CDPATH='' cd -- "$_dir" && pwd)
exec "$_dir/_wta-launch.sh" claude "$@"
#!/bin/sh
_wrapper=$0
case $_wrapper in
/*|./*|../*|*/*) _dir=$(dirname "$_wrapper") ;;
*) _dir=$(dirname "$(command -v -- "$_wrapper")") ;;
esac
_dir=$(CDPATH='' cd -- "$_dir" && pwd)
exec "$_dir/_wta-launch.sh" codex "$@"
#!/bin/sh
_wrapper=$0
case $_wrapper in
/*|./*|../*|*/*) _dir=$(dirname "$_wrapper") ;;
*) _dir=$(dirname "$(command -v -- "$_wrapper")") ;;
esac
_dir=$(CDPATH='' cd -- "$_dir" && pwd)
exec "$_dir/_wta-launch.sh" pi "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment