Skip to content
Worktrees & Branches

Worktrees & Branches

LeapMux is built to run several coding agents at once against the same repository. Git worktrees keep their changes separate: each agent (or terminal) can work in its own linked worktree, on its own branch, with its own working copy. This chapter explains how to choose a branch or worktree when you open a tab, how to change or delete branches later, how to push your work, and how LeapMux protects you from losing uncommitted changes when you close a tab.

For the content that lives inside tabs, see Coding Agents and Terminals. For the git-aware file tree and inline diffs, see File Browser. For the tiling canvas the tabs live in, see Tabs & Layout.

Why per-agent worktrees matter

A single git checkout has one working copy and one current branch. Two agents that share it share that working copy. Each agent sees the other’s edits, staged files, and branch switches. A git checkout by one agent changes the files under the other.

A linked worktree is a second working directory attached to the same repository, checked out on a different branch. With a worktree per agent, each agent gets:

  • An isolated working copy — files one agent edits do not appear in another’s tree.
  • An independent branch — switching or committing in one worktree does not touch another.
  • A contained cleanup — delete a worktree, and its branch, when that line of work ends. The main checkout stays untouched.

LeapMux uses this model throughout: tabs are grouped in the sidebar by repository and then by branch, and the open-time Git options panel lets you create a new worktree from the dialog.

Worktrees are optional. You can also keep working in your repository’s main checkout (“Use current state”) — useful for quick one-off tasks where isolation does not matter.

The Repo → Branch sidebar tree

Open tabs are grouped in the workspace sidebar into a two-level tree:

Repo group   (Repo label)
└─ Branch group   (Branch name + diff-stats badge)
   ├─ tab
   └─ tab
  • The repo group header shows the repository, with the origin URL (or the toplevel path for a local repo with no origin) in its tooltip.
  • Each branch group header shows the branch name and a diff-stats badge summarizing changes in that working directory. Its icon states which kind of checkout the row is: a branch glyph for a branch in the main checkout, a folder-with-arrow glyph for a linked worktree. Hover the name for both facts in words — Worktree branch or Branch with the branch name, and Directory with the path it lives in. When the same branch name sits on more than one Worker, a Worker row names the machine, so two rows that otherwise read alike stay distinguishable before you delete one of them.
  • LeapMux groups tabs by branch name, Worker, and repository path together, so two clones of the same repo on the same branch stay in separate groups.

A working directory with no current branch carries a state label instead. (no branch) means a repository with no commits yet, or a tab LeapMux has not yet stamped with its git state — a new tab shows it for a moment, then picks up its real branch. A detached HEAD carries the short commit SHA (e.g. a1b2c3d). Create new branch moves either one onto a real branch.

The branch context menu

Each branch row has a ... context menu. Right-click the row, or press and hold it on a touch screen, to open the same menu.

ItemWhat it does
Switch to branch…Opens the Change branch dialog on Switch to branch.
Create new branch…Opens the same dialog on Create new branch.
Create new worktree…Opens the same dialog on Create new worktree.
Delete worktree… / Delete branch…Opens the delete dialog (styled in red).

The three change items open one dialog and differ only in which mode it starts on, so the item you pick gives what you then see. You can still switch modes inside the dialog. They keep one set of names on both kinds of row, because a worktree has a branch checked out and the dialog changes that branch either way. The delete item is named after what it removes: Delete worktree… on a linked worktree, which removes a directory, and Delete branch… on a branch in the main checkout, which does not.

Below the delete item, the menu carries an Agents section and a Terminals section — the same ones the tab bar’s + menu holds, acting on this branch instead of on the focused tab:

ItemWhat it does
An agent glyphOpens an agent with that provider on this branch’s checkout, with no dialog.
New agent…Opens the New agent dialog, pre-filled with this branch’s Worker and directory.
New terminal…Opens the New terminal dialog, pre-filled the same way.
A shell pathOpens a terminal with that shell on this branch’s checkout, with no dialog.

The two lists name the branch’s own Worker, not the Worker the focused tab sits on, and LeapMux loads them when you open the menu. The new tab joins the branch’s workspace: LeapMux makes that workspace active first when the branch row belongs to one you are not looking at.

Every item acts through the Worker that hosts the repository. LeapMux greys them all out, with the reason on hover, while that Worker is offline.

Two rows carry no menu at all, because there would be nothing in it to enable. The (no branch) row has no branch to act on. And a row in an archived workspace has nothing it may do: every item either changes branch state or opens a tab, and an archived workspace takes neither. Unarchive the workspace to get the menu back.

A detached-HEAD row keeps its menu, because its short-SHA label is a real label. Delete branch fails there, because the label identifies a commit and not a branch. Use Switch to branch or Create new branch first, to put the working directory on a real branch.

The branch chip in the composer

The composer’s status bar carries the same menu behind a branch-name chip, so you can act on the branch without opening the sidebar. The chip carries the same kind icon as the sidebar row, and hovering it gives the same Worktree branch / Branch and Directory rows. The chip appears when the focused agent reports a branch. Hiding the status bar ([+] ▸ Show status bar) hides the chip; the [+] menu’s own branch row keeps the icon, the hover rows and every item.

Choosing a branch or worktree when you open a tab

When you open a new agent, a new terminal, or a new workspace against a git repository, the dialog shows a Git options panel with five modes (select one with the radio buttons):

ModeWhat it doesFields
Use current stateKeeps the current branch and working copy. The default for new tabs.Shows Currently on branch: <branch>
Switch to branchChecks out an existing branch in this working directory.A branch selector
Create new branchCreates a new branch from a base and checks it out here.Branch Name, Base Branch
Create new worktreeCreates a new linked worktree on a new branch (isolation).Branch Name, Base Branch, Worktree path: preview
Use existing worktreeOpens the tab in a worktree that already exists.A worktree selector

Use current state

No fields. The tab opens in the repository’s current working directory on its current branch. When a current branch exists, the panel shows Currently on branch: <branch>.

Switch to branch

Pick a branch from the selector. The list has a Local and a Remote option group, and (current) marks the branch you are already on.

The panel warns you about three cases. You picked the branch you are already on. You picked a remote branch, and LeapMux checks out the same-named local branch instead. Or the working copy holds uncommitted changes. The switch can then fail, or it can discard those changes.

Create new branch

  • Branch Name — type a name, or click the Generate random name button to fill in a three-word kebab-case slug (e.g. brave-amber-otter). The input placeholder is feature-branch.
  • Base Branch — the branch to start from. It is seeded to the current branch once branches load. Leaving it empty is allowed — the Worker defaults to the current HEAD, which lets you create a branch even on a detached or unborn HEAD.

LeapMux validates the name against its own approximation of git’s check-ref-format rules. A rejected name, or one an existing branch already uses, shows the reason below the input.

A new branch here carries the working copy with it, uncommitted changes included. The panel states this when it finds any.

Create new worktree

Same Branch Name and Base Branch fields as Create new branch, plus a read-only Worktree path: preview. LeapMux always places a new worktree at a fixed location next to the repository:

<repo-parent>/<repo-dirname>-worktrees/<branch>

For example, a repository at ~/code/leapmux with a branch fix-login produces ~/code/leapmux-worktrees/fix-login. The preview is tilde-abbreviated, with the full path in a tooltip. If that path already exists on disk, the operation is rejected.

The worktrees live in a sibling directory of the main checkout, one subdirectory per branch:

On-disk worktree layout:

~/code/                            ◄── repo parent
├── leapmux/                       ◄── main checkout (current branch)
│   └── .git/
└── leapmux-worktrees/             ◄── sibling worktrees directory
    ├── fix-login/      ◄── agent / terminal tab opens here
    │   └── (working copy on branch fix-login)
    └── add-search/     ◄── agent / terminal tab opens here
        └── (working copy on branch add-search)

Each worktree tab opens in one of these branch directories.

A new worktree starts from committed state only. Uncommitted changes in the source working copy stay where they are. The panel states this when it finds any.

Use existing worktree

Pick a worktree from the selector. Each option carries the label <branch> — <tilde-path>. The selector lists linked worktrees only. It leaves out the repository’s main working tree, so you cannot adopt the main checkout as a managed worktree by accident.

Create new worktree is the right choice for “start a fresh task in isolation.” Use existing worktree is for re-attaching a tab to work you (or another agent) already set up.

Changing the branch on a tab

Open the branch row’s ... menu and choose Switch to branch…, Create new branch… or Create new worktree… to open the Change branch dialog on that mode. It works on one repository working directory. It offers three of the five modes: Switch to branch, Create new branch, and Create new worktree.

What each mode does on Apply:

ModeEffect
Switch to branchChecks out the chosen branch in this working directory. Every tab in the group is relabelled to the new branch.
Create new branchCreates the branch from the chosen base and checks it out here. Tabs relabelled to the new branch.
Create new worktreeOpens a brand-new tab in the new worktree — your current tabs stay where they are.

Switch to branch and Create new branch change the working directory under the tabs already in it. An agent or a terminal there does not stop, and from that point it reads the new branch’s files. The dialog states this before you apply.

When you pick Create new worktree in this dialog, an extra Open as selector appears with two choices:

  • Agent — shows an agent provider picker and opens an agent tab in the new worktree.
  • Terminal — shows a Shell picker and opens a terminal tab in the new worktree.

The sidebar labels update as soon as the change completes. The file browser’s git status refreshes too, when it shows the repository you changed.

Switching branches with uncommitted changes can fail or discard work. If the dialog reports uncommitted changes, commit or push them first (see Pushing a branch).

Deleting a branch or a worktree

Open the branch row’s ... menu and choose the delete item. The dialog it opens is titled after what it removes — Delete worktree for a linked worktree, Delete branch for a branch in the main checkout — and its red primary button carries the same words. Alongside it are a Cancel button and, when there is pushable work, a Push button.

The dialog shows a branch status block, a sentence describing which tabs are affected, and a sentence stating what the delete does: a worktree delete removes the directory and the branch, while a branch delete removes the branch and switches the working directory to the branch you pick.

Deleting a linked worktree

There is no “switch to” picker, and the status block notes that the group’s tabs will be stopped. Delete worktree closes every tab in the group and removes the worktree. Once the last tab that points at that worktree is gone, the Worker runs git worktree remove, deletes the branch, and drops its record. It skips the branch delete when another worktree still has that branch checked out, so a branch you added to two worktrees survives the first removal.

The dialog checks that git accepts the removal, then closes and leaves the work running on the Worker. The Worker needs a moment to stop an agent and delete a large working copy, so the directory disappears shortly after the tabs do. A worktree that another tab still uses, or one LeapMux does not track (a directory you created yourself with git worktree add), stays on disk.

If git refuses the removal outright — the worktree is locked, for example — the dialog stays open with the reason, closes nothing, and adds a Close tabs, keep worktree button that closes the group’s tabs and leaves the directory on disk.

Deleting a regular branch

For a branch in the main checkout, you must tell LeapMux where to leave HEAD. The dialog shows Switch this working directory to: and a branch selector listing every branch except the one being deleted. On Delete branch, the Worker checks out your chosen target, then force-deletes the branch you are deleting. Tabs keep running on the switched-to branch.

If the branch you are deleting is the only branch, the selector is replaced by the error Cannot delete the only branch. Create another branch first. and the button stays disabled.

Branch deletion is a force-delete (git branch -D). Unmerged commits on the deleted branch that have not been pushed are gone. If the status block shows unpushed commits, push first.

Pushing a branch

The delete dialog and the Close last tab dialog both offer a push button when the branch has work to push. The delete dialog also needs a tab in the group that carries a working directory, which is the directory it pushes from. The label adapts:

Branch stateButton label
Has uncommitted changesCommit and Push
Clean working copy, but unpushed commits or no remote branchPush

Commit and Push stages everything (git add -A) and makes a WIP commit before pushing. Push just pushes. If the branch has no upstream yet, LeapMux sets one up (git push -u origin <branch>). LeapMux abandons a push that does not complete within 60 seconds.

A push needs an origin remote and a real branch name, so LeapMux cannot push a detached HEAD.

Use Commit and Push as a quick “save my work before I switch or delete” before changing or deleting a branch. The WIP commit captures everything so nothing is lost; you can reword or squash it later.

Branch status indicators

The delete and Close last tab dialogs share a status block that summarizes the branch’s git state. It opens with two labelled rows, then shows some of the lines below depending on the state:

  • Worktree branch or Branch, with the branch name — the same labels and the same glyph the sidebar row carries. A linked worktree has a branch checked out like any other checkout, so the label names the branch and the kind at once.
  • Directory, with the path that checkout lives in, shortened to ~ under your home directory on the Worker.
  • Uncommitted changes: with a diff-stats badge — when the working copy is dirty.
  • N commit(s) not pushed. — when there are unpushed commits.
  • Branch not pushed to remote. — when the branch has no remote counterpart.
  • No uncommitted changes or unpushed commits. — when everything is committed and pushed.
  • A sentence describing the affected tabs (for example, 2 agents and 1 terminal will be stopped, 1 file will be closed.).

The sidebar branch-group header also carries a diff-stats badge (+N -M *U) so you can see at a glance which branches have changes. For the full meaning of those badges and the per-file git status colors, see File Browser.

Dirty-worktree protection when closing tabs

Closing tabs is where you are most likely to lose work, so LeapMux guards the last tab of a worktree or branch. When you close the last tab of a worktree, or the last non-worktree tab on a branch that has uncommitted changes, unpushed commits, or a missing remote, the Close last tab dialog appears.

Its opening sentence states which kind of checkout you are about to close, and the branch status block below names it and gives its directory. Its buttons:

ButtonEffect
CancelAborts the close — nothing happens.
Push / Commit and PushPushes your work first (shown only when there is pushable work).
Delete worktree (worktree targets only)Closes the tabs and schedules the worktree for removal.
Close anywayCloses the tab(s) but keeps the worktree on disk.

If git refuses the removal — the worktree is locked, for example — Delete worktree is unavailable and the reason appears above the buttons. Close anyway still closes the tab.

LeapMux removes a worktree only as part of closing the tabs that point at it, so a removal never deletes a worktree that a live tab still uses. Delete worktree here covers the last tab on that worktree. Delete worktree… on the branch row covers the whole group at once.

Close anyway does not push and does not delete. It closes the tab. Any uncommitted changes stay on disk in the worktree, but you lose the tab that points at it. Use Push / Commit and Push first if the status block shows work you want to keep.
This dialog needs the Worker, because only the Worker reads the branch’s git state. With that Worker offline, the tab closes without the dialog and the worktree is left unreferenced; when the Worker returns, its housekeeping pass reclaims it — directory and branch — unless it holds uncommitted or unpushed work, which is always left for you.

Where git operations run

Every git command runs on the Worker that owns the working directory — the machine where your repository actually lives — not in your browser and not on the Hub. So the Worker computes all the branch and worktree state you see (branches, worktrees, diff stats, ahead/behind) and streams it back over the end-to-end-encrypted Worker channel.

The Worker runs each git command with a fixed C locale and with terminal prompts disabled, so no command blocks and waits for credentials. A push against a private remote therefore fails instead of hanging. Configure a credential helper or an SSH agent on the Worker.

For more on workers and how they are selected, see Managing Workers. For the run modes that host workers, see Running LeapMux.

Last updated on