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.

TargetMeans
-t apiThe session api
-t api:2Window 2 of api
-t api:serverThe window named server
-t api:2.1Pane 1 of window 2
-t .1Pane 1 of the current window
-t !The last window
-t ^ / -t $The first / last window
-t + / -t -The next / previous window
-t %5A pane by its unique id
-t @3A window by its unique id
-t $1A 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.

KeysReaches
prefixThe outer tmux
prefix prefixThe 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
ProblemCause and fix
Tiny window after reattachAn old client is still attached at a smaller size. Use attach -d.
Colors wrong over SSHTERM mismatch. Set default-terminal "tmux-256color" and check the remote terminfo.
Clipboard does not reach your MacEnable set-clipboard on (OSC 52) or use a clipboard forwarder.
Agent forwarding dead in new panesStale SSH_AUTH_SOCK. See the environment section.
Session gone after reboottmux 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.

ToolNotes
tmuxinatorRuby, YAML project files, mature
tmuxpPython, YAML or JSON, can freeze a live session to a file
smugGo, single binary, YAML
sesh / tmux-sessionizerFuzzy-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 hand

tmuxp 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
HookFires
session-created, session-closedSession lifecycle
client-attached, client-detachedClient lifecycle
after-new-window, after-split-windowAfter creating
pane-exited, pane-diedA pane's process ended
window-linked, window-unlinkedWindow added to or removed from a session
alert-activity, alert-bell, alert-silenceAlerts