GitHub CLI extension for managing repositories that use a bare-git worktree layout.
gh wt keeps one bare repository in .bare/ and checks branches out as sibling
worktree directories:
repo/
├── .bare/
├── main/
├── feature-a/
└── feature-b/
Install from GitHub:
gh extension install williamw/gh-wtFor local development, clone and install from the working copy:
git clone https://github.com/williamw/gh-wt.git
cd gh-wt
gh extension install .Clone a repository into the .bare/ layout and create the default branch worktree:
gh wt clone owner/repoclone also writes an executable starter worktree-setup.sh and a
worktree-config.toml into the new repo root, and configures the remote
fetch refspec so git fetch keeps origin/* remote-tracking refs up to
date. The starter setup script validates the new worktree folder and leaves
a commented slot for project-specific setup (copying .env files, installing
dependencies, and so on). Edit it to taste, or delete it if you do not want
the hook to run.
Set up an existing repo — either a bare-git layout that predates the config file, or a plain clone you want converted to the worktree layout:
gh wt initIn a bare-layout repo, init fixes the fetch refspec if it is missing and
scaffolds worktree-config.toml, asking for an optional branch prefix
(Enter for none). If it finds the legacy setup-worktree.sh, it offers to
rename it to worktree-setup.sh; either way the config records the name in
use. In a normal clone, init offers to convert it in place:
.git/ becomes .bare/, the checked-out branch moves into its own worktree
folder, and local branches, stashes, and reflog all survive. Conversion
requires a fully clean tree (no modified, staged, or untracked files).
Add a worktree for a branch:
gh wt add feature-branchBranch names with slashes keep the branch name intact and use the final path segment as the folder name:
gh wt add user/feature-branch
# folder: feature-branch
# branch: user/feature-branchUse --branch-name when the folder name should differ from the branch name:
gh wt add feature-branch --branch-name user/feature-branch
gh wt add feature-branch -b user/feature-branch--base-branch controls the source branch for new worktrees. It does not rename
the new branch. Use -B as the short form:
gh wt add feature-branch --base-branch main
gh wt add feature-branch -B main--local creates a branch locally without pushing to origin. Use -L as the
short form (-l now belongs to --linear):
gh wt add feature-branch --local
gh wt add feature-branch -L--linear names the branch from a Linear issue URL. Use -l as the short
form. The issue ID and title slug become the branch name, and a positional
argument acts as a branch prefix (with or without a trailing slash):
gh wt add billw/ -l https://linear.app/modularml/issue/MKT-176/add-redirect-for-mojo-package-submission-page
# branch: billw/MKT-176-add-redirect-for-mojo-package-submission-page
# folder: MKT-176-add-redirect-for-mojo-package-submission-page
gh wt add -l https://linear.app/modularml/issue/MKT-176/add-redirect-for-mojo-package-submission-page
# branch: MKT-176-add-redirect-for-mojo-package-submission-pageA URL without a title slug yields just the issue ID; query strings, fragments,
and trailing slashes are ignored. --linear cannot be combined with
--branch-name, since both control the branch name.
Pasting a Linear issue URL as the positional argument works without the flag —
it is detected automatically and behaves exactly like -l:
gh wt add https://linear.app/modularml/issue/MKT-176/add-redirect-for-mojo-package-submission-pageEach repo can have a worktree-config.toml in the repo root (next to the
setup script). Both keys are optional:
setup-script = "worktree-setup.sh"
branch-prefix = "billw"branch-prefix prepends <prefix>/ to bare branch names. gh wt add foo
first checks out origin/billw/foo if it exists, then origin/foo, and
otherwise creates a new billw/foo branch. The prefix never applies when the
name already contains a / (full branch names, including other people's
branches), when --branch-name is given (exact names stay exact), or when a
positional prefix is used with --linear (it wins over the config). Linear-
derived branch names do get the prefix.
setup-script names the setup hook, resolved relative to the repo root. When
set, the script must exist and be executable — a missing configured script is
an error, not a silent skip.
Without the key, gh wt add looks in the current directory for an executable
worktree-setup.sh, then the legacy setup-worktree.sh. Finding both is an
error, since the choice is ambiguous — set setup-script to pick one. Repos
still using the legacy name without a config get a one-line hint pointing at
gh wt init.
The hook runs after the worktree is created, with the new folder name as its argument:
./worktree-setup.sh feature-branchIf the setup script exits non-zero, gh wt add exits non-zero too. The created
worktree is left in place.
Remove a worktree:
gh wt rm feature-branchrm resolves the actual checked-out branch from the worktree before deleting
the local branch, so the folder name does not need to match the branch name. Add
-d or --delete-remote to delete the resolved branch from origin too. If
the resolved branch is already missing from origin, rm warns and continues:
gh wt rm feature-branch -dRemove all worktrees whose associated pull requests are merged or closed:
gh wt rm --mergedUse --force to bypass the local branch safety check when you know you want to
delete a worktree with unpushed or untracked work.
If Git refuses to remove a worktree because it contains submodules, gh wt rm
deinitializes submodules in that worktree and retries with --force before
deleting the branch.
gh wt rm <folder> can also remove Git-registered worktrees outside the repo
root, such as temporary worktrees under /private/tmp, by matching the folder
basename shown by gh wt status. If that worktree is detached, gh wt rm
removes the worktree only and skips local or remote branch deletion.
If the worktree folder was already deleted but Git still has stale worktree
metadata for that folder, gh wt rm <folder> automatically runs
git worktree prune -v to clear the stale record. This cleanup does not delete
local or remote branches because the checked-out branch can no longer be safely
resolved from the missing worktree.
List worktrees:
gh wt listShow status for all worktrees:
gh wt status- GitHub CLI (
gh) - Git
- Python 3.10+ (3.11+ when a
worktree-config.tomlis present, fortomllib) - Bare-git layout created by
gh wt cloneorgh wt init, or an existing repo with.bare/
Install dependencies:
pixi installRun tests:
pixi run testInstall the local checkout as the active gh wt extension:
gh extension remove gh-wt 2>/dev/null || true
gh extension install .
gh wt --help