worktree list
List known worktrees managed for the current repository.
Usage
worktree list
worktree list --agents
worktree list --json
worktree list --agents --jsonUse this before open, remove, or cleanup decisions.
Flags
| Flag | Alias | Description |
|---|---|---|
--agents | -a | Show the agent session living in each worktree |
--json | Print one JSON document on stdout instead of the list, and run non-interactively |
Showing agent sessions
--agents finds the agent sessions that are open and names the one found in each worktree. It asks two
sources and joins them on the directory each session runs in, compared as real paths so a symlinked or
differently-cased spelling of the same directory still matches:
- Herdr, through
herdr agent list, whenherdris installed. This finds agents Herdr started for you, including ones the runtime does not list a process for. - The agent runtime, through
<program> agents --json. The program is the first word ofagent.command, or theherdr.agentkind whenagent.commandis unset.
A Herdr agent that matches a runtime session by session id is named by that session; one the runtime does
not list is named <kind>@<pane>, for example claude@w5:p1.
- feature/one (Agent: feature-one-1f)
- feature/two (Ahead: 2, Behind: 1, Agent: feature-two-1f [waiting])
- feature/three (Agent: notes-1f [interactive])
- feature/four (Agent: feature-four-1f [interactive, waiting])
- feature/five (Agent: feature-five-1f [done])- No marker — a background agent that is getting on with its work.
[waiting]— a background agent that is live but not progressing: idle between turns or blocked on a question, so it is probably waiting on you.[done]— a session the runtime reports as finished. It no longer holds anything back, socleanuptreats the worktree as ordinary stale work. The exceptions are a session Herdr still shows in a pane and one whose process is still running, which the runtime has been seen to report as finished: those are not marked[done]. A session Herdr shows holds the worktree back only while Herdr reports it as anything other than idle or done. A running one is weighed by its status like any live session: it is[waiting]when it is idle or blocked on a question, and it holds the worktree back unless it is only idle.[interactive]— a session in a terminal rather than a background one: somebody’s own, or an agentworktree branchstarted through Herdr. It is listed becausecleanupwill refuse to remove a worktree somebody is working in, and this is where you find out which those are.[interactive, waiting]— an interactive session that is live but not progressing, on the same terms as[waiting]: an agent that has finished its turn or stopped on a question. One that is only idle, rather than blocked on a question, does not hold the worktree back.
A worktree with no Agent: detail has no session in it. Where a worktree holds
more than one — a dispatched agent and your own terminal, say — the entry names
one of them, and the marker describes the session it names.
Without --agents, list asks neither source anything and its output is
unchanged. If neither is available — herdr is not installed and neither agent.command nor
herdr.agent is set, or the runtime reports nothing usable — --agents simply lists no sessions
rather than failing.
Ahead and behind counts
Ahead: counts commits this worktree carries that its comparison base does not.
The count is taken for every worktree whose directory is present, including
one on a branch you have never pushed — that is the case worth seeing, because
those commits exist in exactly one place.
The base depends on whether the branch tracks a remote:
| The branch | Ahead: counts against | Behind: |
|---|---|---|
| tracks a remote branch that still exists | its upstream | shown |
| tracks nothing, or a remote branch that is gone | origin/HEAD, falling back to defaultSourceBranch | not shown |
A worktree whose commits are all already applied in that base shows where they landed instead of how many there are:
- feature/bulk-actions (Remote removed, Merged into origin/main)The count is not hidden to be tidy. It is replaced because it would be
misleading: a squash merge lands a branch as one new commit and a rebase
rewrites every commit it touches, so a merged branch stays “7 ahead” of main
forever while holding nothing that is not already there. Ahead: is in this
list to say what removing the worktree would cost, and for that branch the
answer is nothing. Where even one commit is not already in the base, the count
is shown as usual — the whole change is what is compared, so a branch that has
moved on since it was merged counts as carrying work again.
Behind: is only ever counted against an upstream. Against the default branch it
would mean “commits on main this branch does not have”, which is true of almost
every worktree the moment main moves — so where there is no upstream the figure
is left out rather than printed as a 0 that was never counted. A count of zero
is not shown either way; only a non-zero figure earns a line.
If neither origin/HEAD nor defaultSourceBranch resolves, the count cannot be
taken at all, and the entry says so instead of looking empty:
- feature/orphan (Unpushed commits unknown: no comparison base; set defaultSourceBranch or run git remote set-head origin --auto)The same detail appears in remove’s picker and in
cleanup’s report of what it held back, because all
three render a worktree the same way.
JSON output
--json prints exactly one JSON document on stdout, on one line, in place of the list. The spinner lines
and any warning go to stderr. It implies a non-interactive run.
{"worktrees":[{"branch":"feature/one","path":"/abs/repo.worktrees/feature/one","current":false,"pathExists":true,"remote":"origin/feature/one","remoteExists":true,"ahead":2,"behind":1,"mergedInto":null,"uncommittedChanges":0,"safeToRemove":false}]}| Key | Value |
|---|---|
branch, path | the worktree’s branch and absolute path |
current | whether it is the worktree you are in |
pathExists | whether its directory is still on disk |
remote, remoteExists | the upstream the branch tracks, null when it tracks none, and whether that remote branch still exists |
ahead, behind | the counts above as numbers |
mergedInto | the base whose commits already contain all of this branch’s changes; null when not known to be merged |
uncommittedChanges | the number of uncommitted changes; null when the directory is gone, as there is nothing to count |
safeToRemove | the verdict cleanup acts on |
agent | only with --agents: null when no session lives in the worktree, otherwise {name, sessionId, herdrAgent, live, interactive, waiting}. waiting is true for a live session that is idle between turns or blocked on a question, whether or not it is interactive. herdrAgent is the name Herdr gives the agent, as branch --json reports it in herdr.agent, or the agent’s pane id when Herdr reports no name |
A count or flag that could not be taken is null, never 0 and never left out. ahead and uncommittedChanges are
null when the directory is gone (and ahead also when no comparison base resolved), behind is null for a branch with no upstream, and
inside agent any of sessionId, herdrAgent, live, interactive and waiting is null when the
source did not say. A 0 always means the count was taken and is zero.
A failure prints {"error":{"code","message"}} on stdout instead and exits non-zero; the codes are listed under
branch.