GET /api/worktrees returns only worktrees under the managed root
(~/.mulmoterminal/worktrees/<repo>-<hash>/), so a repository whose worktrees were created by
Claude Code's own worktree switch or by hand answers with an empty list even when git worktree list
shows twenty. The worktree overlay is then empty, and there is no way to reach a worktree's
conversation from the UI. Proposal: return them all with a managed flag and let the UI withhold
the destructive actions.
What happens
A repository with 20 worktrees under <repo>/.claude/worktrees/… and <repo>/.worktrees/…:
$ git worktree list | wc -l
20
$ curl -s "localhost:34567/api/worktrees?cwd=<repo>"
{"isGit":true,"base":"develop","worktrees":[]}
Expected: the worktrees the repository actually has, whoever created them.
Why
isManagedWorktree (server/git/worktrees.ts) exists as a delete guard — its comment says so:
"the guard that stops a delete request from touching anything we didn't create". worktree-routes.ts:46
uses that same predicate to decide what to list. Those are different questions. "May this be
removed" has to stay narrow; "does this repository have this worktree" does not.
The cost is not cosmetic. After a restart tmux is gone, and the one thing that can offer a worktree's
conversation back is a row for that directory: dirSession already finds it (its transcripts are
under the worktree's own project directory, and /api/sessions?cwd=<worktree> lists them today).
The overlay simply never asks, because the worktree is not in the list. Saving each worktree as a
cwdPresets chip works as a workaround, which is what makes the missing piece feel like just the
listing.
Proposal
git worktree list --porcelain is already parsed for the whole repository (parseWorktreeList), so
the filter is the only thing dropping the rest.
- Return every worktree, each with
managed: boolean.
- The overlay shows all of them; remove / push / PR stay behind
managed, so the guard's actual
job is untouched and an unmanaged worktree is read-only — MulmoTerminal did not create it and
should not offer to delete it.
worktreeOccupancy (server/session/worktree-session-limit.ts) keeps limiting managed worktrees
only. It already says "Only a MANAGED worktree is limited. An ordinary directory takes as many
terminals as the user wants", and an unmanaged worktree is somebody else's convention — a repo
that puts them in .worktrees/ may well want two cells in one.
Files I would expect to touch: server/git/worktrees.ts (the listing, and a managed field on
WorktreeInfo), server/git/worktree-routes.ts, and the overlay component that renders the rows.
Open question: whether a read-only row is worth it at all, or whether the answer the maintainers
prefer is "use the app's own worktree feature". A repository whose worktree layout is fixed by its
own tooling cannot move to the managed root, which is the case this is filed from.
Environment
- MulmoTerminal 4.10.1, started with
npx mulmoterminal
- macOS 26.6.2, arm64, Node v24.19.0, zsh
- tmux 3.7b, Claude Code 2.1.247
- Browser: Chrome
GET /api/worktreesreturns only worktrees under the managed root(
~/.mulmoterminal/worktrees/<repo>-<hash>/), so a repository whose worktrees were created byClaude Code's own worktree switch or by hand answers with an empty list even when
git worktree listshows twenty. The worktree overlay is then empty, and there is no way to reach a worktree's
conversation from the UI. Proposal: return them all with a
managedflag and let the UI withholdthe destructive actions.
What happens
A repository with 20 worktrees under
<repo>/.claude/worktrees/…and<repo>/.worktrees/…:Expected: the worktrees the repository actually has, whoever created them.
Why
isManagedWorktree(server/git/worktrees.ts) exists as a delete guard — its comment says so:"the guard that stops a delete request from touching anything we didn't create".
worktree-routes.ts:46uses that same predicate to decide what to list. Those are different questions. "May this be
removed" has to stay narrow; "does this repository have this worktree" does not.
The cost is not cosmetic. After a restart tmux is gone, and the one thing that can offer a worktree's
conversation back is a row for that directory:
dirSessionalready finds it (its transcripts areunder the worktree's own project directory, and
/api/sessions?cwd=<worktree>lists them today).The overlay simply never asks, because the worktree is not in the list. Saving each worktree as a
cwdPresetschip works as a workaround, which is what makes the missing piece feel like just thelisting.
Proposal
git worktree list --porcelainis already parsed for the whole repository (parseWorktreeList), sothe filter is the only thing dropping the rest.
managed: boolean.managed, so the guard's actualjob is untouched and an unmanaged worktree is read-only — MulmoTerminal did not create it and
should not offer to delete it.
worktreeOccupancy(server/session/worktree-session-limit.ts) keeps limiting managed worktreesonly. It already says "Only a MANAGED worktree is limited. An ordinary directory takes as many
terminals as the user wants", and an unmanaged worktree is somebody else's convention — a repo
that puts them in
.worktrees/may well want two cells in one.Files I would expect to touch:
server/git/worktrees.ts(the listing, and amanagedfield onWorktreeInfo),server/git/worktree-routes.ts, and the overlay component that renders the rows.Open question: whether a read-only row is worth it at all, or whether the answer the maintainers
prefer is "use the app's own worktree feature". A repository whose worktree layout is fixed by its
own tooling cannot move to the managed root, which is the case this is filed from.
Environment
npx mulmoterminal