yāti (याति) — Sanskrit for "travels" or "goes forth." In the Rigveda, it describes the journeying of gods between realms. yāti lets you travel between worktrees seamlessly, each one a self-contained world.
It is a git worktree manager with tmux and docker-compose integration. Creates and manages isolated worktrees and environments for feature branches. For when you want to vibe code multiple branches of the same repository simultaneously but maintain proximity to the code.
yati manages the full lifecycle of a worktree: file copying, post_create/post_activate/pre_teardown hooks, tmux session layout, and per-worktree docker-compose isolation via automatic port offsetting ([ports]) and environment variable injection ([environment]).
Inspired by opencode-worktree
git clone git@github.com:jacobhm98/yati.git
cd yati
cargo install --path .yati create feature-branchYou can optionally specify a session index with --index. This controls port allocation and must not already be in use:
yati create --index 3 feature-branchThis will:
- Create a new git worktree at
~/.yati/<project>/feature-branch - Create a new branch
feature-branch(or use an existing one) - Copy any configured files into the worktree
- Run any
post_createhooks - Open a new tmux session named
<project>/feature-branch
Switch to an existing worktree:
yati activate project/feature-branchor, if ran from project root
yati activate feature-branchThis will:
- Check that a worktree exists at
~/.yati/<project>/feature-branch - If a tmux session already exists, switch to it
- If the session was lost (e.g., after a reboot), run
post_createhooks and create a new tmux session - Run
post_activatehooks (runs on every activate, whether the session existed or was recreated)
From inside a yati-managed worktree:
yati deactivateThis leaves the current session without destroying it. If you switched from another tmux session, you'll be returned there. If you attached from a bare terminal, you'll be detached from tmux. The session stays alive for later reactivation with yati activate.
From inside a yati-managed worktree:
yati teardownThis runs pre_teardown hooks, removes the worktree, deletes the branch, and kills the tmux session. If you came from another session you'll be switched back; otherwise you'll be returned to your original terminal.
Use --force to remove even with uncommitted changes:
yati teardown --forceyati listShows all yati-managed worktrees across all projects.
When you run multiple worktrees of the same project, docker compose up in each one will clash — container names, networks, volumes, and host-port bindings all collide. yati solves this by automatically injecting environment variables into every tmux pane of a worktree session.
-
COMPOSE_PROJECT_NAMEis set to<project>-<branch>(sanitized for Docker). This isolates container names, networks, and volumes per worktree — no config needed. -
[ports]table — each worktree is assigned an auto-incrementing index (0, 1, 2, ...). For each entry in[ports], the env var is set tobase + index * offset. This gives every worktree unique host ports. -
[environment]table — arbitrary env vars with{{project}}and{{branch}}template support, expanded per worktree.
yati also sets YATI_PORT_OFFSET to index * offset for use in custom scripts.
Each worktree gets the lowest available index starting at 0. The index is persisted in a .yati_index file inside the worktree directory and is freed when the worktree is torn down.
Reference the env vars in your docker-compose.yml with fallback defaults so it still works outside yati:
services:
db:
ports:
- "${DB_PORT:-5432}:5432"
web:
ports:
- "${WEB_PORT:-3000}:3000"With this config:
[ports]
offset = 100
DB_PORT = 5432
WEB_PORT = 3000- Worktree index 0:
DB_PORT=5432,WEB_PORT=3000 - Worktree index 1:
DB_PORT=5532,WEB_PORT=3100 - Worktree index 2:
DB_PORT=5632,WEB_PORT=3200
yati supports dynamic shell completions for subcommands, flags, worktree targets, and branch names. Run the appropriate setup for your shell once:
echo 'source (COMPLETE=fish yati | psub)' >> ~/.config/fish/completions/yati.fishecho 'source <(COMPLETE=bash yati)' >> ~/.bashrcecho 'source <(COMPLETE=zsh yati)' >> ~/.zshrcAfter restarting your shell (or sourcing the file), yati <TAB> will complete subcommands, yati activate <TAB> will complete with existing worktree targets, and yati create <TAB> will complete with git branch names.
yati can generate its own man page:
yati --generate man > ~/.local/share/man/man1/yati.1Then man yati will work. If your system uses a different man path, adjust accordingly (e.g. /usr/local/share/man/man1/).
A tldr page is also included. To make it available to your tldr client:
cp tldr/yati.md ~/.local/share/tldr/pages/common/yati.mdCreate a yati.toml in your repository root:
# Files or directories to copy from the main worktree into new worktrees
copy_files = [".env"]
# Patterns to exclude when copying
exclude = ["*.log"]
# Commands to run after creating a worktree.
# Plain strings run synchronously before the tmux session is created.
# Use { command = "...", async = true } to run in a background tmux window.
post_create = [
"echo setting up",
{ command = "npm install", async = true },
]
# Commands to run every time a worktree is activated (including after creation)
post_activate = ["docker compose up -d"]
# Commands to run before tearing down a worktree
pre_teardown = ["docker compose down"]
# Tmux windows to create in the session.
# The first window replaces the default window; additional entries create new windows.
[tmux]
windows = [
{ name = "editor", command = "nvim" },
{ name = "server", command = "npm run dev" },
{ name = "claude" },
]
# Per-worktree port isolation for docker-compose.
# Each worktree gets an index (0, 1, 2...); port = base + index * offset.
[ports]
offset = 100
DB_PORT = 5432
WEB_PORT = 3000
# Arbitrary env vars injected into the tmux session.
# Supports {{project}} and {{branch}} templates.
[environment]
MY_SERVICE_ID = "{{project}}-{{branch}}"