Introduction
This is a note on how I use tmux to run several coding agents at once, one task each, without losing track of them. It is less about tmux as a way to split a terminal and more about the scripting layer underneath it: a session per task, a git worktree per branch, and a few small scripts bound to keys. I wrote it for anyone already running more than one agent at a time, or about to, who is comfortable with basic tmux, has some familiarity with git worktrees, and wants the workflow part. If tmux or git worktrees are new to you, open the boxes below first.
If you are new to tmux, read this first.
Tmux is a terminal multiplexer. A single terminal window can hold many sessions, each split into windows and panes. The part that matters here is that all of them keep running inside a background server even after you close the terminal or disconnect. You drive it with a prefix key (mine is remapped to Ctrl-a); every shortcut below is that prefix, then another key. There are only a handful worth putting to muscle memory.
If you are new to worktrees, read this first.
A git worktree is a second working directory checked out from the same repository. Normally a repo has one working copy and you switch branches inside it; with worktrees you get several folders on disk at once, each on its own branch, all sharing the same history and remotes. You create one with git worktree add ../some-folder -b my-branch. The reason it matters here is that two agents can work in two folders on two branches without touching each other’s files or fighting over a single checkout.
I started using tmux this year and that helped me avoid a bunch of terminal tabs. Then I started running three or four coding agents at the same time, each on its own branch, plus a few more reviewing PRs, and the parts of tmux I’d never bothered with turned out to be the ones I now can’t work without. So I gave it a weekend. Not the panes-and-windows part I already knew, but the scripting underneath. I spent most of it turning the things I do over and over (start a task, find the one that’s waiting on me, clean up afterward) into one-key workflows. That’s most of what’s below.
The mental model: tmux is an always-on server
What clicked for me is that when I run tmux, I’m starting a daemon. My windows and panes live inside it, not inside the terminal I typed the command in. The terminal I’m looking at is just a client that happens to be attached. So I can quit the terminal entirely, come back later, run tmux attach, and land right back on an agent that’s still typing. The work was in the server the whole time, not the terminal.
I lean on that harder than I expected to. I keep a throwaway second server, tmux -L scratch, completely separate from my real one, for experiments I don’t want anywhere near my actual sessions. And once I started seeing sessions as durable state living in a daemon rather than windows on my screen, the next bit followed on its own: I don’t have to be attached to talk to it.
The repetitive parts are one keystroke now
This is where it starts to pay off. Now that most of my coding work runs through agents, the friction isn’t the work itself, it’s everything around it: cd-ing into the right folder, cutting a branch or a worktree, rebasing on main, and getting to whichever agent is working or stuck. The less time I spend on that setup, the better, and tmux’s scriptability is what collapses it. Every tmux command is just a message to the server about some target, so a plain shell script can read and drive my terminals without me looking at them. Two commands do almost all of it for me: send-keys to type into a pane and capture-pane to read one back:
# what's this session running right now? tmux display-message -p -t api '#{pane_current_command}' # read the last screenful back out, not even attached tmux capture-pane -p -t api # type into it from outside tmux send-keys -t api 'pnpm test' Enter
But I almost never run these by hand. Every routine I repeat gets folded into a small script and bound to a key.
I don’t type the commands in this post by hand. Each one is bound to a keyboard shortcut, my prefix key (Ctrl-a) then a single letter. prefix + s jumps between tasks, prefix + T starts one, prefix + G shows the fleet, and prefix + X clears the finished ones. The shell scripts you’ll see are just what those keys trigger. Each of these is a keystroke, not a line I retype.
Those scripts live wherever you point your keybindings. Mine sit in ~/.tmux/, next to where the plugin manager drops plugins (~/.tmux/plugins/), but nothing about that folder is special. They’re plain shell scripts, so put them where you like.
I end up living inside those #{...} bits in the format strings more than any other feature. They’re a tiny templating language, and I reuse the same little expressions in scripts, in keybindings, and in my status bar. They earn their keep most when it comes to knowing which agent needs me, which I’ll get to.
A session per task
I run each task in its own session, named after the task, agent inside it. I detach, go do something else, come back later, and it’s exactly where I left it. Switching tasks is switching sessions; finishing one is killing its session.
Jumping between them is one keystroke: prefix + s drops down the session list, and I tab through to whichever task I want. The preview panel at the bottom shows what each one is doing, an agent mid-edit or a test run scrolling past, before I even switch.
Without this I’d have a wall of terminal tabs and no idea which one was doing what. But a session on its own is only half of what a task needs. The other half is its files, and I quickly hit the fact that agents can’t share one working directory.
One name, and the rest happens
A git worktree is a second working directory on the same repo with its own branch checked out. Same history, same remotes, but its own files on disk, so two agents can go off and do completely different things without stepping on each other.
What I didn’t love was the bookkeeping. A new task didn’t just need a session. It needed a branch, a folder for that branch, the bits of config git doesn’t track, its dependencies installed, and the session pointed at the folder. Tiny steps, but I do this a lot of times per day, and they all sit right at the start of a task where I least want friction. So I folded the whole thing into one command and bound it to prefix + T: it pops up, asks for a name, and that one name becomes the branch, the folder, and the session. Roughly the shape of it:
# new-task.sh <name> dir="$WORKTREES/$name" if [ ! -d "$dir" ]; then # first time I touch this task git -C "$REPO" fetch --quiet origin main git -C "$REPO" worktree add -b "$name" "$dir" origin/main ln -s "$REPO/.claude/settings.local.json" \ "$dir/.claude/settings.local.json" # the config git ignores fi tmux new-session -ds "$name" -c "$dir" # a session rooted in the folder tmux send-keys -t "$name" 'pnpm install' Enter # deps, on a fresh worktree tmux switch-client -t "$name" # and drop me in
By the time I’ve finished typing the name there’s a branch, a folder, its config, a pnpm install running, and a session I’m already sitting in.
Two of those lines took me a beat to get right, both about the files git doesn’t track:
- The config, I symlink.
.claude/settings.local.json(my local permissions) isn’t in git, so a fresh worktree doesn’t have it. I point a symlink at the one copy in the main checkout, so editing it once updates every worktree at once. node_modules, I install, never share. Symlinking it is tempting, but pnpm tiesnode_modulesto that checkout’s exact dependency graph, so a shared one breaks the moment two worktrees disagree. pnpm hard-links from a global store, though, so a per-worktree install is cheap. So: link the config, install the modules.
I don’t build anything up front, but that part depends on the project. Mine is an Nx workspace that builds what a command needs on demand and caches it, and most of the libraries are imported straight from source anyway, so the build happens the first time I actually run the thing I’m working on, not when I create the folder. A repo with a heavier build step might add a pnpm build to new-task.sh right after the install. The worktree scaffolding is the reusable idea here; what runs once the deps land is yours to tune.
Knowing which one needs me
With four agents going, the thing I actually need is to know which one has stopped and is waiting on me, without opening each session to check. I lean on two things for this: an ambient signal that’s always on, and a board I can pull up on demand.
The ambient one is the iTerm2 tab color. Claude runs a little script of mine whenever it changes state, so the tab goes orange when an agent finishes, red when it’s blocked waiting on me, and back to normal the moment I send it a message. The agent that needs me is lit before I’ve even looked away from whatever else I’m doing.
The fiddly part is that Claude runs those hooks with no terminal attached, so the script has nothing to print an escape code to. What it does instead is ask tmux which tty the session is sitting on, and write straight to that:
# the pane that fired the hook lives on some tty; find it, write to it pane_tty=$(tmux display-message -p -t "$TMUX_PANE" '#{pane_tty}') printf '\033Ptmux;\033\033]6;1;bg;red;brightness;255\007\033\\' > "$pane_tty"
The \033Ptmux;… wrapper is tmux’s passthrough escape: it tells tmux “don’t interpret this, just hand it to the terminal you’re drawing into,” which is how an iTerm2 tab-color code gets from inside tmux out to iTerm. It only works because I’ve turned on set -g allow-passthrough all. (That #{pane_tty} is one of the #{…} format bits from earlier. The real script sets all three color channels, not just red.)
The board I pull up on demand is prefix + G. It capture-panes every session without attaching to any of them, then prints a one-glance view of the whole fleet: who’s idle, who’s working, and who’s blocked waiting on me.
# agent-status.sh, bound to prefix + G for s in $(tmux list-sessions -F '#{session_name}'); do last=$(tmux capture-pane -pt "$s" | grep . | tail -1) # its last line case "$last" in *'(y/n)'*|*waiting*|*'?') state="⏸ waiting" ;; *running*|*building*) state="⏳ working" ;; *) state="✓ idle" ;; esac printf ' %-16s %s\n' "$s" "$state" done
Between the two I leave four agents running and never wonder who’s waiting. The tab lights up on its own, and when I want the whole picture at once it’s one key away. That’s the change that made running several at once feel manageable.
Claude can manage the other sessions
That board reads every pane for me, and the same two commands behind it, capture-pane and send-keys, work just as well from inside an agent as they do from my keyboard. So I gave Claude a small skill that wraps them: it can list the other sessions, read what any of them is showing, and type into one. Now I can tell the agent in front of me to go check on another task or hand something off to it, and it does that without me switching anywhere.
# from one session, read another agent's live screen tmux capture-pane -pt api # and send it somewhere useful tmux send-keys -t api 'rebase on main, then run the tests' Enter
What I didn’t expect is what capture-pane hands back. It’s the other session’s live screen, so it includes whatever that agent is doing right now: the reasoning it’s working through, the tool call it’s part-way into, the question it’s about to ask. One agent can read another’s thinking, not just its final answer. In separate iTerm tabs there’s no way to do this. A tab can’t see into another tab. tmux keeps every session in one server, so anything with access to it, me or an agent I point at it, can read any of the others.
Clearing out finished tasks
The one thing worktrees don’t do is clean up after themselves. Every finished task leaves a folder behind. prefix + T starts a task, so I wrote a second command on prefix + X to end them: it lists the repo’s worktrees, I tick the ones I’m done with, and it removes each folder, kills its session, and deletes the branch in one go. Merged branches go quietly; it stops to ask before dropping anything with uncommitted or unmerged work, so I can clear out a dozen dead tasks at once without throwing away something I forgot about.
What it feels like now
None of this is special. It’s a handful of shell scripts and a few keybindings on top of tmux, and it took me a weekend to put together. What it bought me is that the busywork around running agents, the cd-ing and branching and cleanup, is single keystrokes now, and that was the part I kept getting stuck on.
I’ll keep tweaking the scripts as the way I work changes. If you run more than one agent at a time, I’d start with the worktree-per-task command and the tab colors. Those two did the most for me.