worktree
A multiplexer may bind a git worktree to a workspace as a first-class record — the binding its UI
groups a repo’s checkouts by. herdr does; tmux has no workspace tier and does not. These verbs route
through that binding where it exists and fall back to plain git plus a normal
open where it does not, so the same command works on both. See
multiplexers and worktrees.
cyber-mux worktree add --branch <branch> [--path <path>] [--base <ref>] [--launch <cmd>] [--template <name>] [--at <placement>] [--env <KEY=VALUE>...] [--label <name>]
Section titled “cyber-mux worktree add --branch <branch> [--path <path>] [--base <ref>] [--launch <cmd>] [--template <name>] [--at <placement>] [--env <KEY=VALUE>...] [--label <name>]”Create a git worktree, and open it when given a placement.
--branch <branch>— required; the branch the new worktree checks out.--path <path>— defaults to<parent>/<repo>.worktrees/<branch>, a sibling of the primary checkout, never nested inside it, on every backend — so a path means the same thing everywhere.--base <ref>— start point for the new branch; defaults to the currentHEAD.--launch <cmd>/--env— each implies--at workspace, the only placement a backend can bind a worktree to (asking for something in a pane is asking for the pane).--template <name>— same rules asopen --template: resolved and validated before the worktree is created, and conflicts with--launch/--env.--label <name>— names the opened workspace (seeopen’s label table). Worth knowing what you get without it: becauseworktree addalways passes--path, herdr labels the workspace after the checkout path’s basename — it would use the branch only if it chose the location itself. So--branch feat/deep/namegives you a workspace namednameunless you pass--label.
With none of --at, --launch, or --env this is plain git: it creates the checkout, opens
nothing, and needs no multiplexer. Nothing was opened, so nothing is grouped — use worktree open
to group it later.
# Grouped with the repo on herdr; a working, ungrouped worktree on tmux.cyber-mux worktree add --branch feat/x --at workspace --launch "claude"Prints root, branch, pane, and workspace. A workspace of null means the worktree opened
ungrouped — either the backend binds nothing (tmux), or the placement could not carry a binding
(herdr’s native call always makes a workspace, so a pane or tab placement falls back to plain git).
That is a complete outcome, not a failure: it succeeds, and — where a placement could have grouped
but didn’t — names --at workspace as the fix in a help[N]: block on stdout, inside the
structured payload (not stderr).
--env on herdr’s worktree-bind route is the one route that cannot carry env natively: it degrades
to an env KEY=VALUE prefix on --launch’s command line, or a stderr warning when there is no
command to ride.
Examples
# Plain git: create the checkout, open nothingcyber-mux worktree add --branch feat/x# Create and open, grouped with the repo where the backend supports itcyber-mux worktree add --branch feat/x --at workspace --launch "claude"# Explicit path and start pointcyber-mux worktree add --branch feat/x --path ~/code/my-app.worktrees/feat-x --base origin/maincyber-mux worktree open <path> [--launch <cmd>] [--template <name>] [--at <placement>] [--env <KEY=VALUE>...] [--label <name>]
Section titled “cyber-mux worktree open <path> [--launch <cmd>] [--template <name>] [--at <placement>] [--env <KEY=VALUE>...] [--label <name>]”Open an existing worktree, grouping it with its repo where the backend can bind. This is the remedy
for a checkout made by a bare worktree add — add now, group later. Same flags as worktree add
minus --branch/--path/--base (the worktree already exists); prints the same root, branch,
pane, workspace shape.
Example
cyber-mux worktree open ~/code/my-app.worktrees/feat-x --at workspacecyber-mux worktree list
Section titled “cyber-mux worktree list”Every worktree of the repo, whether each is still needed, and the workspace each is currently
open in. Table columns: branch ((detached) when none), root, workspace.
Markers ride on the column they are about, so no one-bit fact spends a column of its own:
| Marker | Column | Meaning |
|---|---|---|
(*) |
branch |
the primary checkout — every other row is a linked worktree |
(removable) |
branch |
the worktree looks disposable — see below |
(gone) |
root |
the checkout no longer exists on disk; git can prune it |
A root under your home directory is also shortened to ~/…. Every marker and the shortening are
table-only: --format json carries the raw linked, prunable, merged, and dirty booleans
and the absolute root, unmarked.
(removable) — is this worktree still needed?
Section titled “(removable) — is this worktree still needed?”A worktree is marked (removable) when all of the following hold:
- its branch is merged into the repo’s default branch — the work has landed, so removing the checkout destroys nothing the trunk does not already have;
- the checkout is clean — no uncommitted changes, tracked or untracked;
- nothing is open in it — no
workspaceholds it.
The default branch is resolved, never assumed: origin/HEAD when it resolves, otherwise the branch
checked out in the primary checkout. main is never hardcoded.
Two things worth knowing:
- A squash or rebase merge rewrites the commits, so the original branch tip is no longer an ancestor and the worktree is not marked, even though its work landed. The signal errs toward “still needed” on purpose — a missed marker costs you one manual check, a wrong one costs you work.
- The marker is advisory.
worktree listreports; it never removes, and nothing consults(removable)before aworktree remove. The removal gates are unchanged.
When a signal cannot be determined — a detached HEAD, a checkout already gone, no default branch to
compare against — the field is simply absent in --format json and the row is left unmarked.
Nothing is guessed and nothing fails.
Path, branch, linked, prunable, merged, and dirty always come from git, on every backend — only
the workspace binding comes from the multiplexer, so backends can never disagree about a worktree.
Works outside a multiplexer, where every workspace is simply blank.
Cost. One batched call answers merged for the whole repo at once. dirty is a property of a
directory and git has no batched equivalent, so it costs one git status per checkout that is on
disk — 4 + N git calls in total. At ~20 worktrees the whole listing runs in well under half a
second.
Example
cyber-mux worktree listBRANCH ROOT WORKSPACE----------------------- -------------------------------- ---------main (*) ~/code/my-app w19feat/search (removable) ~/code/my-app.worktrees/searchfeat/checkout ~/code/my-app.worktrees/checkout w6Ffix/flaky-test ~/code/my-app.worktrees/flakyold/spike (gone) ~/code/my-app.worktrees/spikefeat/search is merged, clean, and unoccupied — safe to remove. feat/checkout is open in a
workspace, fix/flaky-test has unmerged work or local edits, and old/spike’s checkout is already
gone (git worktree prune clears it).
Scripted use reads the booleans rather than the markers:
cyber-mux worktree list --format json | jq -r ' .worktrees[] | select(.linked and (.prunable | not) and .merged and .dirty == false and .workspace == null) | .root'cyber-mux worktree remove <path> [--force]
Section titled “cyber-mux worktree remove <path> [--force]”Remove a worktree, releasing its workspace if one is bound.
The gates are identical on every backend: it refuses the primary checkout (absolute — --force never
overrides it), tolerates a checkout already gone from disk, and refuses to discard uncommitted
changes unless --force. A refused removal has no side effect: the workspace stays open. When the
gates pass, the workspace is closed before git removes the checkout, so none is ever left pointing
at a directory that no longer exists.
Examples
cyber-mux worktree remove ~/code/my-app.worktrees/feat-x# Discard uncommitted changes in the checkout toocyber-mux worktree remove ~/code/my-app.worktrees/feat-x --force