Claude Code + tmux: the missing manual
A coding agent mid-refactor is the worst possible process to lose to a closed terminal window. tmux is the standard fix, but the generic tmux tutorials skip everything agent-specific: how to lay out one window per model, how to drive sessions from scripts, and the two gotchas (PATH and window names) that bite anyone who automates this. This is the manual we wished existed, learned from building a product on exactly this stack.
Why agents need tmux at all
Claude Code and Codex are attached to your terminal. Close the window, drop the SSH connection, or let the laptop sleep through a network change, and the agent process dies mid-task, taking its in-flight work with it. tmux breaks that coupling: the agent runs inside a tmux server that survives your terminal, and you attach and detach at will. The payoffs for agent work specifically:
- Survive disconnects. A long autonomous run keeps going when your terminal
doesn't. Reattach with
tmux attachand scroll back through everything the agent did while you were gone. - One window per agent. A window for the Opus session, one for Haiku, one for Codex. Persistent, named, and each keeps its own conversation and its own warm prompt cache.
- Scriptability.
tmux send-keyslets tools (or you) type into any session programmatically, which is how launchers and wrappers drive agents.
The garage pattern: one session, one window per model
Resist the urge to make a new tmux session per agent. One named session with one window per agent is easier to attach, list, and clean up:
tmux new-session -d -s garage -n opus
tmux send-keys -t garage:opus 'claude --model opus' C-m
tmux new-window -t garage -n haiku
tmux send-keys -t garage:haiku 'claude --model haiku' C-m
tmux new-window -t garage -n codex
tmux send-keys -t garage:codex 'codex' C-m
tmux attach -t garage
Note that each agent is launched with its model pinned via --model. That's
deliberate, and it's what
Anthropic's own docs
recommend for running different models at the same time: per-terminal pinning rather than
/model switching. Jump between agents with Ctrl-b n,
Ctrl-b p, or Ctrl-b w for the picker.
Reattaching from anywhere
# is the garage running?
tmux has-session -t garage 2>/dev/null && tmux attach -t garage
# from another machine, over ssh
ssh dev-box -t 'tmux attach -t garage || tmux new -s garage'
The || tmux new idiom gives you attach-or-create in one line. The only thing tmux
won't survive is a reboot of the machine running the server; for always-on agents, put the
garage on a box that stays up.
The gotchas nobody documents
1. GUI apps can't find tmux
Anything launched outside a login shell (a macOS app, a launchd job, some CI runners) gets a
minimal PATH that does not include Homebrew. tmux resolves fine in
your terminal and fails with "command not found" from the app. If you script tmux from anything
GUI-launched, probe the real locations instead of trusting PATH:
for bin in /opt/homebrew/bin/tmux /usr/local/bin/tmux /usr/bin/tmux; do
[ -x "$bin" ] && TMUX_BIN="$bin" && break
done We ship exactly this probe in Model Shift because it's the number-one support issue for any tmux-wrapping app on macOS.
2. Window names are not identifiers
tmux happily lets two windows share a name, and windows renumber when others close. Scripts
that target garage:opus by name will eventually type into the wrong agent. When
you create a window programmatically, capture its immutable window ID and target that instead:
WIN_ID=$(tmux new-window -t garage -n opus -P -F '#{window_id}')
tmux send-keys -t "$WIN_ID" 'claude --model opus' C-m
IDs look like @7 and never change or collide. Names are for humans; IDs are for
automation. Every serious agent launcher learns this the hard way.
3. Send-keys needs the trailing Enter
send-keys 'claude' types the text; C-m (carriage return) actually
submits it. Forgetting C-m leaves a command sitting unexecuted in a window you're
not looking at, which with agents means "why has nothing happened for an hour".
4. Scrollback is your agent's audit log
Default scrollback (2,000 lines) is nothing next to an agent's output. Raise it before you need
it, in ~/.tmux.conf:
set-option -g history-limit 50000 When to stop hand-rolling it
Everything above is scriptable, and the script grows: probe tmux, create the garage, one window
per model, track window IDs, relaunch dead agents, remember which window maps to which model.
That maintenance script is, more or less, what Model Shift is. It keeps a
dedicated tmux session with one window per gear, targets windows by ID, probes Homebrew paths,
launches each agent with its model pinned, and puts a stick shift on top so moving between
agents is a flick instead of a Ctrl-b chord. If you love the pattern but not the
plumbing, that's the productized version. (It needs brew install tmux, of course.)
The garage, pre-built
Model Shift manages the tmux garage for you: one persistent, cache-safe window per model, shifted with a lever.
Get Model Shift for Mac ⇣Further reading: How to run multiple Claude Code sessions in parallel · Every way Claude Code switches models without asking