________________________________
< cowsay goes ai agents and tmux >
--------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
A TUI for managing Claude Code agent sessions across git worktrees. Creates a worktree + branch, starts a tmux session, launches the session's agent (claude, codex, opencode, or antigravity), and opens a terminal tab — all in one keypress. Single Go binary, no daemon.
# Homebrew (recommended)
brew tap erickgnclvs/moomux
brew install moomux
# Go
go install github.com/erickgnclvs/moomux@latest
# From source
git clone https://github.com/erickgnclvs/moomux && cd moomux && make installRequires tmux, git, and claude on $PATH.
Linux: moomux detects the terminal it's running in from environment variables. It opens each session as a new tab in GNOME Terminal, Konsole, WezTerm, and kitty (kitty needs allow_remote_control yes + listen_on unix:/tmp/kitty in kitty.conf; without it you get a new window), and as a new window in Ghostty, Alacritty, foot, Tilix, and xterm. Other VTE-based terminals (Ptyxis, GNOME Console, Xfce Terminal, ...) open via gnome-terminal when it's installed. In anything else — including over SSH — moomux shows a tmux attach -t <session> hint instead of failing.
macOS permissions: agents running in moomux sessions get Accessibility and Screen Recording through the moomux binary itself (macOS checks the process that started the tmux server, not tmux or your terminal). Grant $(brew --prefix moomux)/bin/moomux in System Settings → Privacy & Security, then brew services restart moomux. Homebrew releases are signed with a stable certificate, so the grant survives upgrades.
Windows: tmux has no native Windows build. Run moomux inside WSL — the Linux binary above works as-is. In Windows Terminal, moomux opens a new tab and attaches automatically; in any other terminal it prints a tmux attach -t <session> hint instead.
Each session is a tmux window split into two panes, both in the worktree directory:
- Left (~2/3 width) — the agent (
claude,codex,opencode, orantigravity) - Right (~1/3 width) — a plain shell, for
git, tests, etc. alongside the agent
It's a regular tmux window, so regular tmux pane controls apply — mouse click/drag to switch panes or resize (mouse mode is on by default), or the usual prefix keys:
| Action | Keys |
|---|---|
| Switch pane | Ctrl-b then arrow key |
| Cycle panes | Ctrl-b o |
| Split pane vertically (side-by-side) | Ctrl-b % |
| Split pane horizontally (stacked) | Ctrl-b " |
| Zoom/unzoom pane | Ctrl-b z |
| Close pane | Ctrl-b x |
These are plain tmux, not a moomux feature — see man tmux for the full list.
A project can override this default with its own window/pane arrangement — see Custom pane layouts.
For a phone-first setup—including Mosh, durable tmux sessions, mobile approvals, notifications, voice prompting, and diff review—see Moshi with Claude Code.
Over SSH or Mosh, moomux may show an attach command instead of opening a new terminal itself:
tmux attach -t moomux-<session>On a narrow phone display, focus the agent pane and zoom it:
- Press
Ctrl-b, then←to select the agent pane on the left. - Press
Ctrl-b, thenzto make it fill the screen. - Press
Ctrl-b, thenzagain to restore the agent + shell split.
Ctrl-b o cycles between panes, and Ctrl-b d detaches without stopping the
agent. Run tmux ls to find running session names before attaching again.
Tmux pane layout and zoom state belong to the shared window, not to an individual client. If a phone and desktop are attached to the same session, zooming or resizing from either device can affect what the other displays.
moomux launches plain tmux sessions, so tmux settings come from your own config. The first time you run moomux, it offers to add the block below to ~/.tmux.conf for you (only asked once — say no and it won't ask again). To add it yourself instead:
# Essential for Claude to avoid output breaking and desktop notification issues
set -g allow-passthrough on
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'
# Enable native mouse scrolling and selection
set -g mouse on
# Without this, tmux swallows OSC 52 clipboard writes from programs in its
# panes instead of forwarding them to your terminal, so copy-to-clipboard
# silently does nothing when you're attached over SSH or mosh (mosh isn't
# auto-detected — press R in moomux to force copy mode there)
set -g set-clipboard on
# Increase scrollback history for Claude's massive code generations
set -g history-limit 50000
# Start windows and panes at 1 instead of 0 for easier navigation
set -g base-index 1
set -g pane-base-index 1Reload it in any running tmux session without restarting: press Ctrl-b then :, type source-file ~/.tmux.conf, and hit enter. Or from a shell: tmux source-file ~/.tmux.conf.
If moomux already added its block to your ~/.tmux.conf before set -g set-clipboard on existed, it won't be re-offered (moomux only checks that the block is present, not that it's current) — add that line yourself and reload as above.
Each session can be tagged with a ticket and/or PR link (t), shown as icons in the session list and as a row in the detail panel. Clicking either:
- Locally — opens the link in your default browser, as you'd expect.
- Over SSH — copies the link to your clipboard (via the OSC 52 escape sequence above) instead of opening it, since
open/xdg-openwould launch a browser on the remote machine rather than the one you're actually looking at.
SSH is auto-detected. Other transports (e.g. mosh) don't set anything moomux can detect, so press R to force copy mode on (or back off, toggling) — the current state is shown in the ? help overlay.
git clone https://github.com/erickgnclvs/moomux && cd moomux
make build # compile ./moomux
make test # go test ./... -race -count=1
make test-e2e # go test -tags e2e ./e2e/... — real tmux sessions + git worktrees under a temp dir
make install # build + copy to $PREFIX/bin (default ~/.local/bin)
make run # build + run
make clean # remove the built binary, and the copy make install left in $PREFIX/binRequires Go, plus tmux and git (checked by make install/make run via check-deps).
Or build and run directly with go instead of make:
go build -o moomux . && ./moomuxIf you installed moomux via Homebrew, moomux on your $PATH resolves to
Homebrew's bin dir (/opt/homebrew/bin on Apple Silicon, /usr/local/bin on
Intel Macs and Linuxbrew — run brew --prefix to check yours) — and that
typically comes before ~/.local/bin in $PATH, so make install alone
won't make your local build the one that actually runs when you type
moomux, or the one tmux/hooks invoke as a subprocess.
To develop against your own build:
- Make sure
~/.local/bincomes before Homebrew's bin dir in$PATH— e.g. addexport PATH="$HOME/.local/bin:$PATH"near the end of your shell profile (after any Homebrewshellenvline), so it wins unconditionally. make installfrom your working copy.- Open a new shell (or
sourceyour profile /hash -r) so the$PATHchange and shell command cache pick up the change, then confirm withwhich moomuxandmoomux --versionthat it now resolves to your build.
Run make clean to remove the installed dev build ($PREFIX/bin/moomux, in
addition to the local ./moomux binary) and fall back to the Homebrew/Go
install again.
moomuxKeys: ? help (full command list) · n new · enter open · x park · d delete · a archive/restore · A toggle archived view · t tag · e edit session · D diff tool · shift+↑/shift+↓ reorder · tab switch project · q quit
Press ? at any time on the list screen to open a command palette with every keybinding grouped by category, so you don't have to memorize the footer.
Moomux installs commands for parking the current session, tagging it, spawning delegated work, and re-running its worktree setup. It backfills commands for every agent referenced by a configured project or existing session whenever moomux starts, and checks again whenever a session is created or opened. In Claude Code, invoke /kill, /tag, /spawn, or /reseed. In current Codex versions, invoke $kill, $tag, $spawn, or $reseed (or use /skills to select one). Codex skills are installed under ~/.agents/skills/; restart an already-running Codex session once if a newly installed skill does not appear. In Antigravity, invoke /kill, /tag, /spawn, or /reseed too — these are installed as skills under ~/.gemini/config/skills/, which is what Antigravity exposes as slash commands (its workflows/ are deprecated).
$kill parks the session (stops its tmux session and closes its terminal tab) without switching back to the moomux list. Despite the name, it is the same as pressing x, not d: the worktree, branch, and moomux list entry are kept, so the session can be reopened later.
For compatibility, moomux also keeps installing legacy Codex custom prompts under ~/.codex/prompts/. Older Codex CLI versions may expose those as /prompts:kill, /prompts:tag, /prompts:spawn, and /prompts:reseed. Codex and Antigravity skills instruct the agent to run the corresponding moomux CLI command; Claude Code commands can execute it directly. opencode sessions have no equivalent yet — use the moomux list.
The chorded keys have plain-letter alternates for keyboards that can't send modifier+special-key chords (mobile terminal clients, terminals without extended-keys): K/J reorder session, H/L reorder project, [/] switch project.
Press D on a session to review its changes in your own diff tool instead of attaching to it. There's no default — until one is set, D just says so.
Set the command on the settings screen (s, then the "diff tool" row), or in ~/.config/moomux/client.toml:
diff_tool = "diffier"The session's worktree path is appended as the last argument, and the command is started detached — so it should be a GUI app (or anything that opens its own window), not a terminal program.
client.toml is separate from config.toml on purpose: it holds the settings belonging to the machine you're sitting at rather than to the sessions being orchestrated. A TUI attached to a remote moomux serve reads and writes its own copy, so it never configures the server's diff tool.
moomux spawn creates a session non-interactively — no TUI, just a worktree + tmux session + agent, same as pressing n — and optionally types an initial prompt into the agent's pane. Useful for one agent to delegate a sub-task to a fresh session of its own, or for any script/automation:
moomux spawn -project <project> [-name <name>] [-agent claude|codex|opencode|antigravity] \
[-branch <existing-branch>] [-ticket <url>] [-prompt "<initial task>"]It's fire-and-forget: prints the new tmux session's name and exits immediately, without waiting for the agent or reporting anything back. Run moomux spawn -h for the full flag list, or moomux --help for top-level usage.
For a feature that spans several repositories — one spawned session per repo, all of them having to agree with each other — pass -peers <other session names> -contract <path to the shared plan> and spawn appends the coordination rules to the prompt. See Coordinating sessions across several repositories for the rules and why.
Drop an executable script into ~/.config/moomux/userscripts/worktree-create/ and moomux runs it right after every new worktree is created (both from the TUI and moomux spawn), before the agent starts. Drop one into ~/.config/moomux/userscripts/worktree-delete/ and moomux runs it right before a worktree is removed (session delete), while the worktree still exists on disk. Scripts run in name-sorted order, each with a 30s timeout; a failing script only logs a warning and never blocks session creation or deletion.
To scope a script to one project instead of every project, put it under ~/.config/moomux/userscripts/<project>/worktree-create/ or ~/.config/moomux/userscripts/<project>/worktree-delete/ (<project> is the project name as configured in moomux). Global scripts run first, then any project-specific ones for the same event.
These directories live in your global config, outside any repo moomux manages — scripts here are local to your machine and never committed or pushed by a project.
Each script gets these environment variables:
| Variable | Value |
|---|---|
MOOMUX_PROJECT |
project name |
MOOMUX_WORKTREE |
path to the new worktree (also the cwd) |
MOOMUX_REPO |
path to the project's main repo |
MOOMUX_BRANCH |
branch checked out in the new worktree |
Example — copy .env from the main repo into every new worktree (worktrees don't get it since .env is normally gitignored):
#!/bin/sh
# ~/.config/moomux/userscripts/worktree-create/copy-env.sh
if [ -f "$MOOMUX_REPO/.env" ]; then
cp "$MOOMUX_REPO/.env" "$MOOMUX_WORKTREE/.env"
fichmod +x ~/.config/moomux/userscripts/worktree-create/copy-env.shExample — clone ignored build directories (node_modules, target, .next/cache) into new worktrees using copy-on-write where the filesystem supports it, so they don't have to be rebuilt/reinstalled from scratch:
#!/bin/sh
# ~/.config/moomux/userscripts/worktree-create/10-copy-ignored.sh
for d in node_modules target .next/cache; do
[ -e "$MOOMUX_REPO/$d" ] || continue
case "$(uname)" in
Darwin) cp -c -R "$MOOMUX_REPO/$d" "$MOOMUX_WORKTREE/$d" ;;
*) cp -r --reflink=auto "$MOOMUX_REPO/$d" "$MOOMUX_WORKTREE/$d" ;;
esac
doneOnly scripts with the executable bit set are run; anything else in the directory is ignored.