Create a git worktree under <repo-root>/.claude/worktrees/<name> — and, optionally,
launch Claude Code or
Codex in it.
A single dependency-free bash script. It works from anywhere inside a repository, including from another worktree: the path is always resolved against the main repository root, so nested worktree trees never appear.
The default layout (.claude/worktrees/) is the one Claude Code uses itself, so
claude --worktree <name> reuses the worktree created here instead of creating a
second one. Names with slashes are flattened the same way it flattens them —
feat/xy/login lives in .claude/worktrees/feat+xy+login, never in a
feat/xy/ subtree — because Claude Code only looks directly under that
directory. The branch keeps its slashes.
curl -fsSLO https://raw.githubusercontent.com/DefeatMan/cworktree/main/cworktree
chmod +x cworktree
mv cworktree ~/.local/bin/ # anywhere on your $PATHRequires bash (3.2+, so the stock macOS bash is fine) and git 2.9 or newer.
A child process cannot change its parent's directory, so --cd only prints a
path and a small shell wrapper does the cd. --shell-init emits that wrapper
plus tab completion (the wrapper is also what makes --cd -c cd first and start
claude afterwards):
# ~/.bashrc
eval "$(cworktree --shell-init bash)"
# ~/.zshrc (after compinit)
eval "$(cworktree --shell-init zsh)"cworktree fix-login # new branch fix-login from HEAD
cworktree fix-login origin/main # new branch tracking origin/main
cworktree hotfix v1.2.3 --detach # detached worktree at tag v1.2.3
cworktree --checkout feature/x # attach an existing branch
cworktree fix-login --claude # ... then launch Claude Code in it
cworktree fix-login --effort high # ... with a high effort level
cworktree fix-login --max # ... same as --effort max
cworktree fix-login -x # ... launch Codex instead
cworktree fix-login -x --max # ... at max effort
cworktree fix-login -c -- fix the login # ... with a prompt for the session
cworktree fix-login --cd # cd into the worktree
cworktree fix-login --cd -c # cd into it, then launch claude there
cworktree --cd .. # cd back to the repo root
cworktree fix-login -D # remove the worktree again
cworktree fix-login review -D # ... or several worktrees at once
cworktree -D # prune worktrees deleted by hand
cworktree -l # list worktrees + their HEAD commitOptions may come before or after the branch name, and --cd, -D/--delete
and --checkout take their name from the positional argument when they are
not given one directly, so cworktree fix-login --cd and cworktree --cd fix-login mean the same thing — likewise cworktree --checkout feature/x and
cworktree feature/x --checkout. -D/--delete goes on taking names: every
positional argument on the line is one more worktree to remove.
A worktree's name is always its branch's — the two are never configured
apart. By default a new branch is created from HEAD; if the base branch
looks like <remote>/<branch> but is unknown, cworktree fetches it once
before giving up. --checkout attaches an existing branch instead of
creating one; --detach checks out a commit with no branch at all, so the
worktree's name is then just a name of its own.
A branch name may contain /, but the worktree directory it gets is one
level deep: every / becomes a +, so feat/xy/login is
.claude/worktrees/feat+xy+login with the branch still called
feat/xy/login. That is exactly what Claude Code does with a --worktree
name, and it is what lets it find this worktree; a nested
.claude/worktrees/feat/xy/login would be invisible to it, and
claude --worktree feat/xy/login would build a second worktree of its own
alongside. Either spelling addresses the same worktree afterwards, so
--cd feat/xy/login and --cd feat+xy+login (the name --list prints) both
work.
The worktree path is the only thing printed on stdout (everything else goes to
stderr), so it composes: cd "$(cworktree scratch)", or cworktree scratch -p
to get the path without creating anything.
The base directory is added to .git/info/exclude (not .gitignore, so nothing
in your repository changes) unless git already ignores it, or --no-exclude is
given.
| Option | Meaning |
|---|---|
-c, --claude [args] |
after a successful create, launch Claude Code in the worktree |
-x, --codex [args] |
the same for Codex: codex [args], run inside the worktree. Mutually exclusive with -c; [args] and $CWORKTREE_CODEX_ARGS work exactly like -c's |
-e, --effort <level> |
pass --effort <level> to claude (low, medium, high, xhigh, max); implies a session, -c unless -x is given. Also as its own flag: --low, --medium, --high, --xhigh, --max. With -x the same five levels go to codex as -c model_reasoning_effort="<level>" |
-B, --reset-branch |
reset the branch to the base branch if it already exists |
--checkout <name> |
attach an existing branch instead of creating one; the worktree is named after it, exactly like a new branch |
--detach |
detached HEAD at the start point, no branch |
-f, --force |
pass --force to git worktree add, or to git worktree remove with -D/--delete (twice for a locked worktree) |
-d, --dir <path> |
base dir for worktrees, relative to the repo root |
--no-exclude |
do not touch .git/info/exclude |
--no-fetch |
do not auto-fetch when a remote branch is unknown |
-p, --path |
print the worktree path and exit |
-n, --dry-run |
show what would happen, change nothing |
--cd [name] |
print the path of an existing worktree so the shell wrapper can cd into it; --cd .. is the repo root. With -c/--claude the shell cds there first and launches claude afterwards |
-D, --delete [name...] |
remove the worktree (git worktree remove); several names remove several worktrees; without a name, prune the bookkeeping of worktrees whose directory is gone (git worktree prune) |
-l, --list |
list the worktrees under the base dir, one line each: git log --oneline -1 --decorate of that worktree's HEAD, so the decoration reads (HEAD -> <its own branch>). When the listing holds a single worktree it is shown the way git itself would — that same line followed by git status --short, run in the worktree, colours included. At a terminal only: piped or captured it prints bare names, one per line, so it stays usable for scripting and tab completion |
--shell-init [sh] |
see above |
-- |
everything after this is joined into a single string and passed to claude as its [prompt]; implies -c |
-Dused to be the short form of--dir, which is now-d/--dir.-b/--branchhas been removed: a worktree is always named after its branch, so there is no longer a separate name to give it.
Re-running the same command is a no-op if the worktree already exists (with
-c/-x, it just starts a new session there).
-c/--claude runs, in the current terminal:
claude --worktree <name> --name <name> [args]
The name is the one that resolves to the worktree just created, so the session
runs in it instead of in a new one: for feat+xy+login on disk that is
--worktree feat/xy/login, since Claude Code maps the slashes back to pluses
itself and rejects a + in a name given to it. The same name is passed to
--name, which sets the session's display name: an unnamed --worktree
session is one Claude Code deletes on a clean exit — worktree and branch both
— so naming it turns that into a prompt instead, and lets claude --resume <name> find the same worktree again later. When the worktree's name cannot
be made to agree with what claude --worktree accepts — a base dir other than
.claude/worktrees/, a name longer than the 64 characters it allows, or a
worktree still nested by an older cworktree — cworktree says so and starts a
plain claude inside the worktree instead, with neither flag: Claude Code
never created that worktree, so it never auto-deletes it either.
-x/--codex starts Codex instead, as codex [args] run from inside the
worktree. There is no --worktree to pass: codex's own always creates a new
worktree rather than pointing at one that is already there, so there is no name
for the two sides to agree on — and nothing that would delete the worktree on
exit either. Starting the session in the directory is also what lets
codex resume find it again, since its picker filters by the directory a
session ran in. -c and -x are mutually exclusive: one terminal, one session.
An effort level is the one thing translated rather than passed through, because codex takes it as a config override instead of a flag — the five level names are the same on both sides:
cworktree fix-login --max # claude --worktree fix-login --name fix-login --effort max
cworktree fix-login -x --max # codex -c model_reasoning_effort="max"No claude or codex flags are hardcoded, apart from --worktree/--name and
the effort level when you ask for one (-e/--effort, or the shorthands
--low … --max). Asking for an effort level implies a session, since it is
only useful with one; it is inserted before the arguments below, so anything
you pass yourself comes later on the command line — including your own
--name, if you would rather override the default.
Extra arguments come from, in order of precedence:
-c '<args>'(or-x '<args>') — a single string, parsed like a shell command line, so quoting works:cworktree fix-login -c '--chrome -p "look at the tests"'. The string is only recognised as agent arguments when it starts with-and is not a cworktree option; for anything else write--claude='<args>'or--codex='<args>'.$CWORKTREE_CLAUDE_ARGS, or$CWORKTREE_CODEX_ARGSfor-x— your default, used when the flag is given without arguments of its own. Each is only read for its own agent, so both can be set at once.- After
--— everything left is joined with spaces into one argument and appended last, which is whatclaudeandcodexboth take as their[prompt]:cworktree fix-login -- look at the failing tests. Since it is a single argument, put agent flags in-c '<args>'rather than after--.
# ~/.bashrc or ~/.zshrc
export CWORKTREE_CLAUDE_ARGS='--chrome'
export CWORKTREE_CODEX_ARGS='--search'
--dangerously-skip-permissionsis a convenient thing to put in$CWORKTREE_CLAUDE_ARGSfor throwaway worktrees, but it disables every permission prompt for that session. Opt into it deliberately.
| Variable | Meaning |
|---|---|
CWORKTREE_CLAUDE_ARGS |
default arguments for claude with -c/--claude |
CWORKTREE_CODEX_ARGS |
default arguments for codex with -x/--codex |
CWORKTREE_DIR |
default base dir for worktrees (see -d/--dir) |
--cd prints the path of an existing worktree; with the shell wrapper installed
your shell moves there. It never creates anything on its own — a typo is an
error, not a new worktree — but combined with -c/--claude or -x/--codex
(which do create one when it is missing) the shell cds in first and then starts
the session, so you are left in the worktree when it exits:
cworktree fix-login --cd -c --high # create if needed, cd there, then claude
cworktree fix-login --cd -x # ... or codex
cworktree --cd .. # back to the repo root-D/--delete removes a worktree with git worktree remove; add -f when it
has uncommitted or untracked files (twice when it is locked). The branch is left
alone. Empty parent directories are cleaned up as well, which is how a worktree
that an older cworktree nested is migrated: remove it, then create it again to
get the flat directory Claude Code can reuse. Without a name it runs git worktree prune, which cleans up after
worktree directories that were deleted by hand:
cworktree fix-login -D # git worktree remove <path>
cworktree fix-login -D -f # ... even when it is dirty
cworktree -D # git worktree prune
cworktree -D -n # show what that would doAny number of names may be given, in either order — cworktree a b c -D and
cworktree -D a b c are the same list. Nothing is removed until every name has
resolved to a registered worktree, so a typo at the end of the list cannot take
the beginning of it with it, and the same worktree named twice is removed once.
After that the list is worked through to the end: one worktree that refuses to
go — dirty, or locked — leaves the others removed and only shows up in the exit
status.
cworktree a b c -D # remove three worktrees
cworktree -D a b c # ... the same list
cworktree -D a b c -f # ... even the dirty ones
cworktree -D a b c -n # show what that would do