On this pageOverview
Git and review

Worktrees and branches

Add a branch or worktree, start one from an issue, run a setup command in every new worktree, start and stop a run command in one, run a teardown command before one goes, fan out several attempts, see a rebase in progress on its row, remove what you are done with, and restore a worktree you deleted.

Try it
hooli· Spaces
hooli-search
mainmain/
fix/search-rankingsearch-ranking/
feat/token-cachetoken-cache/
spike/old-indexfolder missing
hooli-mail
Graphfix/search-ranking↑1 ↓09 loaded
Weight title hits over body hitsfix/search-rankingRH12 minutesa71e5c9
Trim the query before parsingRH1 hour3d90b4e
Add rank tests for weightsRH2 hoursf2c8a61
Log slow queries over 200msmainJD5 hours9f3c2a1
Cap page size at 50MH1 day41be07d
Bump hono to 4.6GB2 daysc07a9e4
Add a /health endpointNB3 days5d21f88
Tokenize on unicode word breaksBG4 dayse8a4b10
First BM25 indexDC1 week0b6d3f2
hooli-search with three worktrees and one whose folder was deleted outside Tori. Click a worktree for its graph and a commit for its files. Use + to add a worktree (try bug/search-ranking to see the folder name fall back to a full slug), or the ... menu to remove one or prune.

Branches or worktrees

A project is either a Repo or a Bare project (see Spaces, projects and branches):

  • In a Repo, every branch shares one working folder. Only one branch is checked out at a time, and switching branches changes the files under every agent and terminal working there.
  • In a Bare project, every branch is checked out in its own folder, a worktree, beside the others. Two agents on two branches never touch each other’s files, and switching between them checks nothing out.

Use Bare for anything where agents work in parallel. Create one with New in “space” > Clone, where Worktree layout is on by default, turn a folder into one with Initialize git > Bare + worktree, or convert an existing clone with Convert to worktree layout.

Add a branch or worktree

  1. Right-click the project and choose Add branch (Repo) or Add worktree (Bare).
  2. In Add branch in “api” (or Add worktree), type in the filter. The list shows every branch on disk, local and remote together, told apart by their glyph. Branches already checked out say checked out, or in a worktree.
  3. Pick an existing branch, or type a new name. A name that matches nothing gets its own row, Create branch name from main.
  4. For a new branch, click the base after from to pick where it starts. It defaults to the repository’s default branch, and the picker filters as you type.
  5. Press Enter, or click the button.

What happens depends on what you picked:

You picked In a Repo In a Bare project
A local branch Adds its row. Select it or use Checkout to switch Creates a worktree for it
A remote branch Adds a local branch tracking it Creates a worktree tracking it
A new name Creates it from the base and checks it out Creates it from the base, in a new worktree

A new worktree’s folder is named after the branch’s last part, so bug/critical gets the folder critical. If that folder exists, the folder uses the whole branch name instead, bug-critical; if both exist, Tori refuses rather than overwrite anything.

The new branch or worktree is selected, and a new worktree opens a chat draft so you can start right away.

Fresh branches from the remote

The list is read from disk, so it opens at once. Branches pushed to the remote since the last background fetch are not in it until you click the Fetch button in the list’s header. Tori fetches every repository on its own every 10 minutes, and when you come back to the window; set how often with Git fetch every in Settings > Integrations > Remote, or turn it off. A repository whose remote would ask for a password is skipped by the background fetch rather than prompting you.

Delete a branch from the list

Hover a local branch that is not checked out and click its trash icon. The dialog asks Delete branch name first, and says whether it has commits the remote does not, so you do not lose unpushed work by accident.

Start from an issue

When the project’s host is connected (see Pull requests and issues), the dialog has a Branch and Issue switch.

  1. Switch to Issue. The list shows the issues assigned to you in the project’s issue repos, which is its own repo until you list others. An issue from another repo is shown with its repo, such as gettori/tickets#31. A repo your account can’t read is named under the list as Not searched, no access.

  2. Pick one, or type an issue that is not in the list and choose Open issue. You can type:

    • a number, 214 or #214, looked up in every issue repo
    • tickets#31: the listed repo named tickets, or with none listed, the tickets repo under the project’s owner
    • gettori/tickets#31: that repo, listed or not
    • the issue’s URL

    A number found in one repo opens straight away. One found in several, such as #31 in the project’s repo and gettori/tickets#31, lists a row for each; pick the one you meant. A pull request with that number is not offered, and with no issue at all the dialog says No issue found.

  3. The branch name fills in from the issue. Change it if you like, and pick a base for a new branch.

  4. Leave Link the branch to the issue on GitHub ticked to have GitHub create the branch and show it on the issue. It is greyed out when the base is not on the remote, or the branch already exists here, and a note under it says why.

  5. Confirm with Start from #214.

The branch or worktree is created, its row wears the issue’s number (click it to open the issue), and a chat draft opens with the issue’s title and body as the prompt and a from #214 link above the composer. Read it, add anything, and send.

Worktree settings

Every Bare project’s settings dialog has a Worktrees section: right-click the project, choose Project settings and pick Worktrees. It holds what each worktree gets: a setup command when it is created, a run command you start and stop by hand, a teardown command before it is removed, and the files shared into it.

A setup command for new worktrees

A new worktree has only what git tracks: no node_modules, no build output, no generated files. A setup command fixes that.

A repo’s commands are under Tooling rather than Worktrees. The repository can also propose these commands itself, see Commands from the repository.

  1. Open Project settings for the project and pick Worktrees.
  2. Type the command in Setup command, for example pnpm install --frozen-lockfile --prefer-offline.
  3. Press Enter or click away to save it.

From then on:

  • It runs in every worktree Tori creates for the project: from Add worktree, from an issue, from fan out, from tori worktree new and from the Autopilot.
  • It runs with sh -c in the new worktree, with TORI_PROJECT_ROOT set to the project’s folder and TORI_WORKTREE_PATH to the new worktree.
  • It runs in the background, not in a tab. When it ends, a toast says Setup finished in critical, or Setup in critical failed (exit 1) with Show log to read its output.
  • A setup that runs longer than 30 minutes is stopped and reported as failed. Removing the worktree stops a setup still running in it.
  • A worktree made from a fork’s pull request never runs it, since that code is not yours.

Agents started over the socket wait for it: turn this on to make tori worktree new, tori spawn and the Autopilot wait until the command exits, up to four minutes, so a scripted session does not start work in a half installed tree. Sessions you start by hand never wait.

The command is kept in your own settings file, keyed by the project, not in the repository, so a repository cannot give itself a command to run. An empty field runs nothing.

A run command for each worktree

The command you start in every worktree, usually the dev server, can be set once for the project and started from the worktree’s row.

  1. Open Project settings for the project and pick Worktrees.
  2. Type the command in Run command, for example pnpm dev --port $TORI_PORT.
  3. Press Enter or click away to save it.

From then on:

  • Each worktree row in the sidebar shows a play button while the pointer is on it. Click it to start the command in that worktree, and click the square that replaces it to stop it. While it stops, the button is greyed out and says Stopping. Off the row the button stays hidden, and a running server shows by its port.
  • The command palette has Start the run command and Stop the run command for the worktree in front. Both are listed only for a project with a run command. Start says Already running while one runs, and Stop says Stopping while one stops and Nothing is running here when nothing does.
  • It runs in the background, not in a tab, with sh -c in the worktree and TORI_PROJECT_ROOT, TORI_WORKTREE_PATH and the worktree’s ports set. While it runs, Run command sits at the top of the Scripts section with the command beside it. Click it, or right-click for Show log, to read its output, and right-click for Stop.
  • With dev server links on, its server’s port shows on the row a few seconds after it starts listening.
  • With Open the browser when a server starts on, which it is by default, the first port its server listens on opens in your browser, as soon as it answers, even with the Tori window hidden. When the server listens on more than one port, the worktree’s $TORI_PORT wins if it is one of them, and the lowest port otherwise. That is one tab per start: a second port opens nothing until you click it, and a run stopped before it listens opens nothing. Tori watches for two minutes after the start. With the browser extension installed, restarting the run brings the tab already on that port to the front instead of opening another. Turn the switch off in Project settings > Worktrees to start servers quietly.
  • With Start the run command when a worktree is created on, a worktree you create from the sidebar, from a branch or from an issue, starts its run command by itself once its setup command finishes without an error, or at once when the project has no setup command. Together with Open the browser when a server starts, making a worktree ends with its app open in the browser. A setup command that fails or runs past its 30 minute limit starts nothing, and so does picking a branch that already has a worktree. Worktrees made by an agent, by tori worktree new, for a pull request review or as attempts never start it, so a fan out does not start a server per worktree. It is off by default, and the switch is greyed out until the project has a run command.
  • Stopping sends it a polite signal first and gives it five seconds to clean up, then ends it. Starting it again while it stops waits for the old one, so two servers never fight over one port.
  • If it exits on its own with an error, a toast says Run in critical failed (exit 1) with Show log. Stopping it says nothing.
  • Removing the worktree stops it before the teardown command runs, and quitting Tori stops every run command. If Tori crashes, the next launch stops the run commands it left running.
  • Removing worktrees automatically never removes a worktree whose run command is running.
  • A log past 5 MB is cut back to its last 1 MB, and run logs older than a week are deleted when Tori starts.
  • A worktree made from a fork’s pull request never runs it, since that code is not yours. Its row has no play button and the palette does not list the two commands there.

Like the setup command, it is kept in your own settings file, not in the repository. An empty field leaves the play button off the rows.

A teardown command before removal

Some worktrees leave things behind that git knows nothing about: a test database, a container, a cache outside the folder. A teardown command cleans them up.

  1. Open Project settings for the project and pick Worktrees.
  2. Type the command in Teardown command, for example docker compose down --volumes.
  3. Press Enter or click away to save it.

From then on:

  • It runs before every removal of a worktree in the project: Remove worktree, removing worktrees automatically, promoting an attempt (for the attempts that go) and removing a Topic member’s worktree.
  • It runs with sh -c in the worktree, while the folder is still there, with TORI_PROJECT_ROOT, TORI_WORKTREE_PATH and the worktree’s ports set. A setup or run command still running there is stopped first.
  • The row shows Tearing down while it runs.
  • A teardown that runs longer than 2 minutes is stopped. Whether it fails or is stopped, the removal still goes ahead, and a toast says Teardown in critical failed (exit 1) with Show log. A teardown that works says nothing, because the worktree going away already says it.
  • A worktree made from a fork’s pull request never runs it, since that code is not yours.

Like the setup command, it is kept in your own settings file, not in the repository. An empty field runs nothing.

Shared files

The same page lists the files shared into every worktree, such as a .env. See Shared in worktrees.

Fan out several attempts

Fan out starts two, three or four independent attempts at the same task, each in its own worktree on its own branch, so you can run an agent in each and keep the best result.

  1. Right-click a Repo or Bare project and choose Fan out. The project needs at least one commit.
  2. Answer Fan out: what are these attempts for? with the goal, for example retry webhooks with backoff. It is kept with the attempts so you can still see later what they were for.
  3. Pick How many attempts?: 2, 3 or 4.

Tori creates the attempts from the current commit, on branches named after the goal, retry-webhooks-with-back-1, -2 and so on (the first 24 characters of the goal). They live in a hidden .tori-attempts folder inside the project, and show in the sidebar grouped under one heading with the goal.

To save time, each attempt gets a copy of the project’s node_modules, vendor, target and .venv folders if they exist. On macOS this copy is almost free. A toast lists any of these that could not be copied, so you can install them in the attempts yourself. The setup command runs in each attempt too.

Start a chat in each attempt (New session on its row, or select it and open a chat), give each the same or a different prompt, and compare.

Keep the winner

Right-click the attempt you want and choose Promote this attempt.

Promoting never merges anything. Merge or open a pull request from the winner’s branch as you would from any other.

A rebase or merge in progress

While a rebase, merge, cherry-pick or revert is under way in a worktree, or in the checked-out branch of a Repo, its row says so:

  • The branch icon turns amber and stays still. It does not pulse, since a pulse means an agent is in the middle of a turn.
  • During a rebase the row shows the branch being rebased, such as feat-login, rather than (detached), which is what git itself reports until the rebase finishes. The breadcrumb in the toolbar shows the same name.
  • A chip at the start of the line under the name says Rebasing 3/7, the commit the rebase is on out of all it replays, or Merging, Cherry-picking or Reverting.
  • When the operation stops on a conflict, the chip turns red and says Conflict 3/7, followed by how many files are conflicted, such as 2 files. Hover the line for the whole sentence, for example The rebase of feat-login stopped on a conflict in 2 files, at commit 3 of 7. Resolve, then continue it.

The row follows git as it works: the chip moves on with each commit, and the row goes back to normal once the operation finishes or is aborted, including when you run git yourself in a terminal. To resolve the conflict and finish, see Merge conflicts.

Remove a worktree or branch

Right-click the row and choose Remove worktree (Bare) or Remove branch (Repo, for a branch that is not checked out).

The dialog shows what you are about to remove before you confirm: the branch, the folder, whether it is clean, has uncommitted changes or unpushed commits, and how many tabs and agents are running in it. Two options:

  • Delete local branch (git branch -D): on by default. Off keeps the branch so you can make a worktree for it again later.
  • Delete remote branch (git push –delete): off by default. Also deletes it on the remote.

Confirming moves the selection to the next worktree in the project (or the one before it, or the main checkout), stops the tabs and agents running there, runs the project’s teardown command if it has one, then removes the worktree and the branches you chose. The folder disappears in one step rather than file by file, so the Changes panel never fills with deleted files while it goes. If deleting the remote branch fails, the local removal still happens and the failure is reported.

What Tori keeps when you remove one

Removing a worktree that holds work you have not saved anywhere else keeps a copy of that work first. A worktree gets a copy when it has any of:

  • uncommitted changes, new files included
  • a rebase, merge, cherry-pick or revert in progress
  • commits that no branch, remote branch or tag holds. With Delete local branch ticked, the branch being deleted does not count, so its unpushed commits are kept too.

The copy holds the worktree’s commits and every changed and new file as they were at that moment, including files you resolved halfway through a rebase. It does not hold:

  • files your .gitignore covers, such as node_modules or .env.local
  • files over 50 MB that are new or changed, so one stray recording or dump does not make the removal slow
  • what was staged and what was not: everything comes back unstaged
  • the operation itself: a restored worktree has the commits and files, and you redo the rest of the rebase from there

The removal dialog tells you which case you are in. When there will be a copy, it says Tori keeps a copy of its commits and changed files for 30 days, so you can restore it. When ignored or large files would go with the folder, it lists them under Ignored and Too large and warns, for example, This deletes ignored files and files too large to keep, which no copy holds. It cannot be undone. With an operation in progress, the status says, for example, rebase in progress, and a warning repeats the row’s sentence and adds Deleting the worktree loses its progress. When files would be lost or an operation is in progress, the button reads Remove anyway.

The copy is made after the tabs and the run command are stopped and before the teardown command runs. If Tori cannot write it, the worktree is not removed and the error says why. Copies are made whichever way the worktree goes: from the sidebar, from a Topic, or as a losing attempt of a fan out.

A copy is a commit under refs/tori/rescue/ in the repository’s own git store, so it is not a branch and git branch does not list it. Tori deletes copies older than 30 days when it starts.

Restore a deleted worktree

  1. Right-click the project and choose Restore deleted worktree. The entry is only there when the project has a copy to restore, and it is offered on Repo and Bare projects alike.
  2. In Restore a deleted worktree in api, pick the copy. Each row shows the branch, the operation it was deleted in, such as rebase 3/7, and how long ago it was deleted. The newest is picked to begin with, and the filter narrows the list by branch.
  3. Below the list, check what comes back: the changed files, the commits no branch holds, the files that were too large to keep, and a note when it was deleted in the middle of an operation. The bottom of the dialog shows where the worktree was.
  4. Click Restore.

Tori makes a new worktree, puts the kept files back in it, unstaged, and then runs the project’s setup command, so the setup sees the restored files. Your repository’s git hooks run as they do for any new worktree. With Start the run command when a worktree is created on, the run command starts once setup finishes. The restored worktree is selected, and the copy is deleted.

The branch it lands on:

The original branch The restored worktree is on
Still there, at the same commit, and not checked out anywhere The original branch
Deleted The original branch, made again at the kept commit
Moved on, or checked out in another worktree A new branch, name-restored, at the kept commit

So a branch that has newer work, or a worktree you made again on the same branch, is never touched. A copy taken on a detached HEAD lands on a branch named restored- followed by the last part of the copy’s id. In a Bare project the worktree folder is named after the branch, as for Add worktree; in a Repo it goes under .tori/worktrees in the repository.

Clean up after deleting outside Tori

If a worktree folder was deleted in Finder or a terminal, git still has a record of it. Prune worktrees on the project clears those records and says how many it pruned.

Remove worktrees automatically

Tori can remove worktrees for you. Both settings are off until you turn them on, under Cleanup in Settings > Integrations:

  • Remove worktrees after merge: removes a worktree once its pull request has merged and the worktree has nothing newer than the commit that merged. A squash merge counts, and so does a branch whose remote was deleted after the merge. This only covers projects in the active Space, because those are the ones Tori asks the host about.
  • Remove idle worktrees after: Off, 3, 7, 14 or 30 days. Removes a worktree once that long has passed since it was created, since its last commit, and since a session in it last did anything, whichever is latest. Its branch must have no unpushed commits. This covers every Space.

Tori never removes:

  • the main checkout, a Topic’s worktree or a fan-out attempt
  • a worktree on a detached HEAD, which has no branch to bring it back with
  • a worktree with uncommitted changes
  • a worktree an agent is running in, or one with a terminal or editor tab open
  • a worktree whose run command is running
  • the worktree you have selected
  • a worktree an autopilot item is still working in

It checks when Tori starts, when you change either setting, every hour, and for a project’s merged branches whenever pull request status refreshes. Just before removing a worktree it checks for uncommitted changes once more.

The branch is always kept, so Add Worktree brings any of them back. Files your .gitignore covers, like node_modules or .env.local, are removed with the folder, the same as removing it by hand. Files in Shared files are only links, so they are safe. When a check removes anything, a message lists the branches.

When the upstream was force-pushed

If someone force-pushed the remote branch over commits you have, pushing would overwrite their update and pulling would merge two histories. Tori notices: the branch’s sync mark says the upstream was force-pushed over your commits, and the branch menu offers Reset to upstream, which resets your branch to the remote’s. Commit or stash anything you want to keep first.

The commit graph

Graph on a branch’s menu opens its commit graph in a tab: every commit as a lane, newest first. Click a commit to see the files it changed, and a file to open that commit’s diff. The same graph is in the Changes panel’s history section. See The Changes panel.