tmux Cheatsheet
Scripting and Layouts
Use this tmux reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Scripting a Layout
Because every tmux action has a command-line form, a project layout is just a shell script. Build it detached, then attach at the end.
#!/usr/bin/env bash set -euo pipefail S=api cd ~/projects/api tmux has-session -t "$S" 2>/dev/null && exec tmux attach -t "$S" # Window 1: editor tmux new-session -d -s "$S" -n editor -c ~/projects/api tmux send-keys -t "$S:editor" 'nvim .' Enter # Window 2: server on top, logs below tmux new-window -t "$S" -n server -c ~/projects/api tmux send-keys -t "$S:server" 'npm run dev' Enter tmux split-window -t "$S:server" -v -p 30 -c ~/projects/api tmux send-keys -t "$S:server" 'npm run test:watch' Enter # Window 3: a plain shell and a git pane tmux new-window -t "$S" -n shell -c ~/projects/api tmux split-window -t "$S:shell" -h -c ~/projects/api tmux send-keys -t "$S:shell.2" 'git status' Enter tmux select-window -t "$S:editor" tmux attach -t "$S"
The has-session … && exec tmux attach guard makes the script idempotent: run it once to build the workspace, run it again to return to it.
Targets
Most scripting bugs are target bugs, so it is worth being precise.
| Target | Means |
|---|---|
-t api | The session api |
-t api:2 | Window 2 of api |
-t api:server | The window named server |
-t api:2.1 | Pane 1 of window 2 |
-t .1 | Pane 1 of the current window |
-t ! | The last window |
-t ^ / -t $ | The first / last window |
-t + / -t - | The next / previous window |
-t %5 | A pane by its unique id |
-t @3 | A window by its unique id |
-t $1 | A session by its unique id |
Unique ids (%5, @3) never shift when things are renamed or renumbered, which makes them the safe choice inside a script.
tmux display-message -p '#{pane_id}' # capture the id you just created PANE=$(tmux split-window -P -F '#{pane_id}') tmux send-keys -t "$PANE" 'htop' Enter
Listing and Introspection
tmux ls tmux list-sessions -F '#{session_name}: #{session_windows} windows' tmux list-windows -a -F '#{session_name}:#{window_index} #{window_name}' tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command}' tmux display-message -p '#{pane_current_path}' tmux info # server diagnostics tmux show-environment tmux show-environment -g
The -F flag with a format string turns any list command into structured output you can pipe into grep, awk, or fzf.
# Fuzzy switch sessions tmux ls -F '#{session_name}' | fzf | xargs -r tmux switch-client -t
Environment Variables
A long-lived tmux server holds a stale environment, which is the usual cause of "SSH agent forwarding stopped working after I reattached".
set -g update-environment "DISPLAY SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION"
tmux set-environment -g EDITOR nvim tmux set-environment FOO bar # session scope tmux show-environment -g tmux setenv -r SSH_AUTH_SOCK # remove it, so the pane inherits fresh
A common fix is to point SSH_AUTH_SOCK at a stable symlink that your shell refreshes on login, so every new pane finds a live agent regardless of when the server started.
Nested tmux
Running tmux inside tmux (typically local plus remote over SSH) means two servers competing for one prefix.
| Keys | Reaches |
|---|---|
prefix | The outer tmux |
prefix prefix | The inner tmux |
prefix a (with bind a send-prefix) | The inner tmux |
# Toggle the outer tmux off so every key goes to the inner one bind -T root F12 \ set prefix None \;\ set key-table off \;\ set status-style "fg=colour245,bg=colour238" \;\ refresh-client -S bind -T off F12 \ set -u prefix \;\ set -u key-table \;\ set -u status-style \;\ refresh-client -S
Simpler alternative: give the remote tmux a different prefix. If local is Ctrl-a and remote is Ctrl-b, there is nothing to toggle.
tmux Over SSH
The rule is to run tmux on the remote host. That is what survives the disconnect.
ssh host -t tmux new -A -s main # attach or create, in one flag (2.9+) ssh host -t tmux attach || ssh host -t tmux new -s main ssh host -t 'tmux attach -d -t main' # steal the session from a stale client
| Problem | Cause and fix |
|---|---|
| Tiny window after reattach | An old client is still attached at a smaller size. Use attach -d. |
| Colors wrong over SSH | TERM mismatch. Set default-terminal "tmux-256color" and check the remote terminfo. |
| Clipboard does not reach your Mac | Enable set-clipboard on (OSC 52) or use a clipboard forwarder. |
| Agent forwarding dead in new panes | Stale SSH_AUTH_SOCK. See the environment section. |
| Session gone after reboot | tmux is not persistent across reboots. Use tmux-resurrect plus tmux-continuum. |
Session Managers
For anything beyond a couple of scripts, a manager keeps layout definitions declarative.
| Tool | Notes |
|---|---|
tmuxinator | Ruby, YAML project files, mature |
tmuxp | Python, YAML or JSON, can freeze a live session to a file |
smug | Go, single binary, YAML |
sesh / tmux-sessionizer | Fuzzy-find a directory and open it as a session |
# ~/.config/tmuxinator/api.yml
name: api
root: ~/projects/api
windows:
- editor: nvim .
- server:
layout: main-horizontal
panes:
- npm run dev
- npm run test:watch
- shell:
panes:
- git status
-tmuxinator start api
tmuxp load api
tmuxp freeze api > ~/.tmuxp/api.yaml # capture a session you built by handtmuxp freeze is the pragmatic path: build the layout by hand until it feels right, then freeze it into a file.
The Sessionizer Pattern
One binding that fuzzy-finds a project directory and opens (or switches to) a session for it. This replaces most manual session management.
#!/usr/bin/env bash # ~/bin/tmux-sessionizer dir=$(find ~/projects ~/work -mindepth 1 -maxdepth 1 -type d | fzf) || exit 0 name=$(basename "$dir" | tr . _) if ! tmux has-session -t "=$name" 2>/dev/null; then tmux new-session -ds "$name" -c "$dir" fi if [ -n "$TMUX" ]; then tmux switch-client -t "$name" else tmux attach -t "$name" fi
bind f run-shell "tmux neww ~/bin/tmux-sessionizer"Note -t "=$name": the = prefix forces an exact match, so has-session -t "=api" does not match a session called api-old.
Hooks
Hooks run commands when tmux events fire.
set-hook -g session-created 'display "session #S created"' set-hook -g after-new-window 'select-layout tiled' set-hook -g pane-exited 'select-layout tiled' set-hook -g client-attached 'refresh-client -S' set-hook -g window-linked 'renumber-windows'
tmux show-hooks -g
tmux set-hook -gu session-created # unset it| Hook | Fires |
|---|---|
session-created, session-closed | Session lifecycle |
client-attached, client-detached | Client lifecycle |
after-new-window, after-split-window | After creating |
pane-exited, pane-died | A pane's process ended |
window-linked, window-unlinked | Window added to or removed from a session |
alert-activity, alert-bell, alert-silence | Alerts |