Docs/CLI reference

CLI reference

tama worktrees remove

Removes every linked git worktree under the roots you name, previewing the whole pass unless --apply is given, and refusing the entire pass before deleting anything when a selected worktree is dirty or locked.

Removes every linked git worktree under the roots you name, previewing the whole pass unless --apply is given, and refusing the entire pass before deleting anything when a selected worktree is dirty or locked.

#Invocation

tama worktrees remove [--root <PATH>]... [--except <PATH>]... [--apply] [--force] [--json]

#Inputs and options

OptionContract
--root <PATH>Repeatable, and required at least once. Each value is a directory under which Tama looks for the repositories that own worktrees. There is deliberately no default root
--except <PATH>Repeatable, and optional. Each value is the path of one linked worktree to leave completely alone. A value that matches no worktree this pass would otherwise consider is a usage error
--applyPerforms the removal. Without it the command previews and mutates nothing
--forceProceeds past dirty and locked worktrees and discards their uncommitted changes. Without it either condition refuses the pass
--jsonPrints the exact document below instead of the text report, in both preview and applied mode

There is no argument that selects one worktree for removal: the pass is every linked worktree found under the roots, minus the ones you name with --except. Narrow it by naming narrower roots, and see what it would touch with tama worktrees list or with this command's own preview.

#Keeping one worktree with --except

--except is repeatable and takes the path of one linked worktree. An excepted worktree is left completely alone: it is not removed, it is not counted as removable, and it never produces a refusal. It appears in the document under the excepted array, so an applied pass reports which worktrees it deliberately did not touch.

It exists because the refusal is the whole pass, which is the right contract and also means one worktree in use blocks every other one. On the machine this option was written for, 39 worktrees could not be removed because a fortieth was being written by a live agent session, and root selection could not separate them because a root selects owning repositories — one repository owned both the worktree in use and many of the others. Naming that one path is how the removal proceeds without destroying another session's in-flight work.

An --except value that matches no worktree this pass would otherwise consider is refused rather than ignored, so a mistyped path can never silently remove the thing it meant to keep. Path comparison is canonicalised, the same way the code already compares a main worktree path.

tama worktrees list has no --except: it removes nothing, so it has nothing to exempt, and its document carries no excepted array. This is a remove option only.

#Discovery and removal mechanics

tama_violations::repo_discovery's discover_repos(root) finds the owning repositories under each root; git itself is then the authority on worktrees, through git -C <repo> worktree list --porcelain. The repository's own main worktree is never reported and never removed. A worktree is dirty when git -C <worktree> status --porcelain prints anything.

Each removal is git's own operation, never a directory delete:

git -C <repo> worktree remove [--force] <path>
git -C <repo> worktree prune

--force is passed through to git worktree remove only when you passed it. The prune runs after the removals so the owning repository's .git/worktrees administrative entries do not outlive the checkouts.

#Output document

Under --json, exactly:

{"schemaVersion":1,"roots":[String],"repositories":[{"repository":String,"worktrees":[{"path":String,"branch":String|null,"head":String,"dirty":Boolean,"locked":Boolean,"prunable":Boolean}]}],"worktreeCount":Number,"unreadable":[String],"unreadableDirectories":[{"path":String,"reason":String}],"excepted":[String],"applied":Boolean,"removed":[String],"refused":[{"path":String,"reason":"uncommitted-changes"|"locked"}]}

It is the list document plus four fields, with excepted immediately after unreadableDirectories.

excepted holds the canonicalised path of every worktree you named with --except, so the document states what the pass deliberately left alone; it is [] when you named none. Those worktrees appear nowhere in removed and nowhere in refused, in a preview and in an applied pass alike.

applied is true only for an --apply pass that actually ran: it is false for every preview, and false for an --apply pass that was refused. removed holds the paths git actually removed, so a preview and a refused pass both report "removed":[]. refused names every worktree that blocks the pass — each with the reason "uncommitted-changes" or "locked" — and it is populated in a preview too, so a bare remove shows exactly what --apply would refuse. --force empties refused in both modes, because nothing blocks a forced pass.

unreadableDirectories is the list report of what the walk could not read, and it ends this pass: an all-or-nothing removal cannot be all-or-nothing over a tree it could not see, so a pass carrying one of these answers "applied":false, "removed":[], prints could not read <path>: <reason> followed by this pass could not read N directories under the roots, so it cannot say what they hold; nothing was removed, and exits 1. This holds for a preview too, because neither answer covers the tree the operator named.

The same document is the answer of tama request worktrees/remove with body {"roots":[String],"except":[String],"apply":Boolean,"force":Boolean} (tama request), where except is optional and defaults to empty. An empty roots array answers status 400 with the missing-root sentence below; a refused pass answers status 200 with the refusal document and the request exits 0, where the CLI exits 1.

#State effect

A bare remove changes nothing: it is a preview, and it exits 0 even when its refused array is non-empty, because a preview has nothing to refuse yet. Only remove --apply deletes, and then only through git worktree remove followed by git worktree prune. The refusal check runs across the whole selection before the first removal, so an applied pass either removes every worktree it selected or removes none of them — Tama does not delete the first three worktrees and then discover the fourth is dirty.

#Refusal boundary

Passing no --root is a usage error, exit 2:

tama worktrees needs at least one --root; a machine-wide default would walk directories Tama does not own.

An --except value that matches no worktree this pass would otherwise consider is a usage error, exit 2:

tama worktrees does not know the worktree <path>; --except takes the path of a worktree this pass would otherwise remove.

--apply without --force refuses the whole pass before deleting anything if any selected worktree carries uncommitted changes or git reports it locked, and names every one of them, one sentence per worktree, exit 1:

Refusing to remove <path>: it carries uncommitted changes. Commit them in that worktree, or pass --force to discard them.
Refusing to remove <path>: git reports it locked. Unlock it with git worktree unlock, or pass --force.

Both sentences name the repair you probably want first — commit in that worktree, or git worktree unlock — and the switch that overrides it. --force proceeds and discards those changes; that is the whole meaning of the flag, and it is why it is not the default.

A refused --apply pass reports "applied":false and "removed":[] with every blocker in refused: the refusal is the whole pass, not the worktrees that happened to be clean.

A failed git worktree remove or git worktree prune is an operational failure, exit 1. Otherwise the command exits 0: every preview does, and so does an applied pass that removed everything it selected, including the pass that found nothing to remove.

Return to the worktrees command group or the complete CLI tree.