terminal multiplexer workflow guide

Master tmux.

The definitive guide to building a persistent, scriptable, multi-pane terminal workflow around Neovim, Git, Claude Code, and your entire TUI toolchain.

~/.tmux.conf
# attach-or-create: the daily driver
tmux new -As myproject
# sessionizer: fuzzy-find & switch projects
tmux-sessionizer
# persistent popup: Claude Code overlay
prefix + y # summon/dismiss
01 // Fundamentals

The tmux hierarchy

Tmux organizes everything into three levels: sessions contain windows (tabs), which contain panes (splits). Think of sessions as project contexts, windows as task groups within a project, and panes as side-by-side views for a single task.

Sessions

One per project or context. Survive disconnects and reboots (with plugins). Switch instantly with fzf.

tmux new -As webapp
tmux new -As api-server
tmux new -As dotfiles
prefix + s → session tree
prefix + ( / ) → cycle
prefix + $ → rename

Windows

Tabs within a session. Name them for tasks: editor, server, tests, logs.

prefix + c → new window
prefix + , → rename
prefix + 0-9 → jump
prefix + n / p → next/prev
prefix + l → last window
prefix + w → window chooser

Panes

Splits within a window. Editor + terminal, logs side-by-side, etc.

prefix + | → vertical split
prefix + - → horizontal split
prefix + z → zoom toggle
prefix + ! → break to window
prefix + q → show pane numbers
prefix + ; → last active pane
The -A flag. tmux new -As name attaches to "name" if it exists, creates it otherwise. Use this as your default. Never manually check for existing sessions again.

Session groups — multi-monitor power

Session groups share windows but maintain independent focus. Attach a grouped session on your second monitor and switch windows independently. This is the correct solution for multi-monitor tmux — not two separate sessions.

session-groups.sh
# Primary session tmux new-session -s main # On second monitor — grouped session, independent window focus tmux new-session -t main -s monitor2
02 // Configuration

Production-ready tmux.conf

A complete, annotated configuration targeting tmux 3.2+. Every line exists to solve a real pain point. Copy wholesale or pick sections.

~/.tmux.conf — core
# ── Terminal capabilities ── set -g default-terminal "tmux-256color" set -as terminal-overrides ",xterm-256color:RGB" # Undercurl support (Neovim LSP diagnostics) set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm' set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' # ── Performance ── set -sg escape-time 0 # CRITICAL for Vim: eliminate ESC delay set -g history-limit 50000 set -g focus-events on # Required for Neovim autoread/gitgutter set -g mouse on # ── Indexing ── set -g base-index 1 # Windows start at 1 (matches keyboard) setw -g pane-base-index 1 set -g renumber-windows on # ── Prefix key ── unbind C-b set -g prefix C-a bind C-a send-prefix # C-a C-a sends literal C-a
Server restart required. Changes to default-terminal and terminal-overrides only apply when the tmux server starts. source-file won't apply them — you must tmux kill-server and restart.
~/.tmux.conf — navigation
# ── Intuitive splits (preserve working directory) ── bind | split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" bind c new-window -c "#{pane_current_path}" unbind '"' unbind % # ── Vim-style pane movement ── bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # ── Repeatable resize (hold prefix then repeat) ── bind -r H resize-pane -L 5 bind -r J resize-pane -D 5 bind -r K resize-pane -U 5 bind -r L resize-pane -R 5 # ── Prefix-free Alt-arrow navigation ── bind -n M-Left select-pane -L bind -n M-Right select-pane -R bind -n M-Up select-pane -U bind -n M-Down select-pane -D # ── Reorder windows ── bind -r S-Left { swap-window -t -1; select-window -t -1 } bind -r S-Right { swap-window -t +1; select-window -t +1 }
Split terminology gotcha. split-window -h creates panes side-by-side (horizontal layout, vertical divider). -v stacks them. The flag describes the direction of the new pane, not the divider orientation.
~/.tmux.conf — copy mode
# ── Vi copy mode ── setw -g mode-keys vi bind -T copy-mode-vi v send -X begin-selection bind -T copy-mode-vi C-v send -X rectangle-toggle bind -T copy-mode-vi V send -X select-line bind -T copy-mode-vi y send -X copy-pipe-and-cancel "pbcopy" # Linux: "xclip -in -selection clipboard" # Wayland: "wl-copy" # WSL: "clip.exe" # Also copy on mouse drag end and Enter bind -T copy-mode-vi Enter send -X copy-pipe-and-cancel "pbcopy" bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel "pbcopy" # ── OSC 52 clipboard (works over SSH!) ── set -s set-clipboard on set -g allow-passthrough on # tmux 3.3+
~/.tmux.conf — status bar (Tokyo Night)
set -g status-position bottom set -g status-interval 5 set -g status-justify left set -g status-style "bg=#1a1b26 fg=#a9b1d6" set -g status-left-length 30 set -g status-left "#[fg=#7aa2f7,bold] #S #[fg=#3b4261]│ " set -g status-right-length 80 set -g status-right "#{?pane_synchronized,#[bg=red] SYNC ,}#[fg=#3b4261]│ #[fg=#a9b1d6]#h #[fg=#3b4261]│ #[fg=#7aa2f7,bold]%H:%M " setw -g window-status-format " #I:#W#F " setw -g window-status-style "fg=#565f89" setw -g window-status-current-format " #I:#W#F " setw -g window-status-current-style "fg=#7aa2f7,bold" set -g pane-border-style "fg=#3b4261" set -g pane-active-border-style "fg=#7aa2f7"
~/.tmux.conf — quality of life
# Reload config with feedback bind r source-file ~/.tmux.conf \; display "Config reloaded!" # Quick backward search bind / copy-mode \; send-keys ? # Toggle mouse on/off bind m set-option -g mouse \; display "Mouse: #{?mouse,ON,OFF}" # Toggle pane synchronization bind S setw synchronize-panes # Preserve manual window names set -g allow-rename off # Resize to smallest viewer of THIS window, not session setw -g aggressive-resize on # Silence bells setw -g monitor-activity on set -g visual-activity off set -g visual-bell off set -g bell-action none
03 // Layouts

Built-in layouts, visualized

Tmux ships with five preset layouts accessible via prefix + Alt-1 through Alt-5, or cycle with prefix + Space. Click a layout below to see it in action.

webapp 1:editor* 2:server 3:tests 14:32
Control the main pane size with :set main-pane-height 40 or :set main-pane-width 120. The main-vertical layout is ideal for an editor taking 2/3 of the screen with terminal panes stacked on the right.
05 // Neovim Integration

Seamless tmux + Neovim

The key integrations: unified navigation between tmux panes and Neovim splits, shared clipboard via OSC 52, and correct color/undercurl rendering.

vim-tmux-navigator

Use Ctrl-h/j/k/l to move between tmux panes and Neovim splits transparently. The plugin detects whether the active pane is running Neovim and routes the key accordingly.

tmux.conf
# Via TPM set -g @plugin 'christoomey/vim-tmux-navigator'
lazy.nvim plugin spec
{ "christoomey/vim-tmux-navigator", keys = { { "<C-h>", "<cmd><C-U>TmuxNavigateLeft<cr>" }, { "<C-j>", "<cmd><C-U>TmuxNavigateDown<cr>" }, { "<C-k>", "<cmd><C-U>TmuxNavigateUp<cr>" }, { "<C-l>", "<cmd><C-U>TmuxNavigateRight<cr>" }, }, }

Clipboard: OSC 52

The modern approach. OSC 52 escape sequences tell your terminal to set the system clipboard — works transparently over SSH. Neovim 0.10+ has built-in support:

init.lua
vim.g.clipboard = { name = 'OSC 52', copy = { ['+'] = require('vim.ui.clipboard.osc52').copy('+'), ['*'] = require('vim.ui.clipboard.osc52').copy('*'), }, paste = { ['+'] = require('vim.ui.clipboard.osc52').paste('+'), ['*'] = require('vim.ui.clipboard.osc52').paste('*'), }, }
OSC 52 terminal support: Works in iTerm2, Alacritty, Kitty, WezTerm, Ghostty, Windows Terminal. Does not work in GNOME Terminal (VTE) or macOS Terminal.app — use xclip/xsel instead.

Verify your setup

terminal tests
# True color — should show a smooth gradient curl -s https://gist.githubusercontent.com/lifepillar/09a44b8cf0f9397465614e622979107f/raw/24-bit-color.sh | bash # Undercurl — should show wavy underline printf '\e[4:3mUndercurl\e[0m\n' # Colored undercurl — wavy + orange printf '\e[4:3m\e[58:2:206:134:51mColored\e[0m\n'
06 // Copy Mode

Copy mode & scrollback

Enter with prefix + [. With vi mode keys enabled, you get full vim-style navigation.

Enter copy mode
prefix+[
Navigate
h j k l
Half-page up / down
Ctrl-u Ctrl-d
Search backward / forward
? /
Begin selection
v
Rectangle selection
Ctrl-v
Select line
V
Yank & exit
y
Top / bottom of scrollback
g G
Quick backward search shortcut
prefix+/
copy-pipe-and-cancel copies to tmux's buffer AND pipes to your clipboard utility simultaneously, then exits copy mode. The variant copy-pipe (without -and-cancel) stays in copy mode — useful for grabbing multiple selections.
07 // Plugins

The essential plugin stack

Install TPM first, then manage everything declaratively from tmux.conf.

TPM bootstrap
# One-time install git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm # Auto-install for dotfiles bootstrap (add to tmux.conf) if "test ! -d ~/.tmux/plugins/tpm" \ "run 'git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm && \ ~/.tmux/plugins/tpm/bin/install_plugins'"
~/.tmux.conf — plugins
set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'tmux-plugins/tmux-sensible' # Sane defaults set -g @plugin 'tmux-plugins/tmux-yank' # System clipboard set -g @plugin 'christoomey/vim-tmux-navigator' # Seamless navigation set -g @plugin 'tmux-plugins/tmux-resurrect' # Persist sessions set -g @plugin 'tmux-plugins/tmux-continuum' # Auto-save + restore set -g @plugin 'fcsonline/tmux-thumbs' # Vimium-style copy set -g @plugin 'sainnhe/tmux-fzf' # Fuzzy find everything # ── Resurrect + Continuum config ── set -g @resurrect-capture-pane-contents 'on' set -g @resurrect-strategy-nvim 'session' set -g @continuum-restore 'on' set -g @continuum-save-interval '15' # MUST be last line run '~/.tmux/plugins/tpm/tpm'
Install plugins
prefix+I
Update plugins
prefix+U
Save session (resurrect)
prefix+Ctrl-s
Restore session
prefix+Ctrl-r
tmux-thumbs quick copy
prefix+Space
Continuum gotcha: tmux-continuum hooks into status-right. If a theme plugin overwrites it, place continuum after the theme in your plugin list, or it silently stops saving.
08 // Workflows

Project workflow patterns

📁

Session-per-project

One named session per repo. Switch instantly. Each session has windows for editor, server, tests, git.

🔍

fzf sessionizer

Fuzzy-find over project directories, auto-create sessions on demand. The ThePrimeagen pattern.

📋

tmuxinator templates

Declarative YAML layout definitions per project. Reproducible dev environments.

🔲

Popup TUI overlays

lazygit, btop, Claude Code as floating popups. Dismiss and resume without losing state.

fzf session switcher

~/bin/tmux-sessionizer
#!/usr/bin/env bash selected=$(find ~/dev/work ~/dev/personal -mindepth 1 -maxdepth 1 -type d | fzf) [[ -z $selected ]] && exit 0 selected_name=$(basename "$selected" | tr . _) if ! tmux has-session -t="$selected_name" 2>/dev/null; then tmux new-session -ds "$selected_name" -c "$selected" fi tmux switch-client -t "$selected_name"
Bind it
bind -r f run-shell "tmux neww ~/bin/tmux-sessionizer"

Scripted project session

~/bin/tmux-webapp
#!/bin/bash SESSION="webapp" PROJECT="$HOME/projects/webapp" tmux has-session -t "$SESSION" 2>/dev/null if [ $? != 0 ]; then tmux new-session -d -s "$SESSION" -n editor -c "$PROJECT" tmux send-keys -t "$SESSION:editor" "nvim ." Enter tmux split-window -h -p 30 -t "$SESSION:editor" -c "$PROJECT" tmux new-window -t "$SESSION" -n server -c "$PROJECT" tmux send-keys -t "$SESSION:server" "npm run dev" Enter tmux new-window -t "$SESSION" -n tests -c "$PROJECT" tmux send-keys -t "$SESSION:tests" "npm run test:watch" Enter tmux select-window -t "$SESSION:editor" tmux select-pane -L fi tmux attach-session -t "$SESSION"

tmuxinator YAML template

~/.config/tmuxinator/webapp.yml
name: webapp root: ~/projects/webapp startup_window: editor pre_window: nvm use 18 windows: - editor: layout: main-vertical panes: - nvim . - # empty shell - server: layout: even-horizontal panes: - frontend: - cd frontend && npm run dev - backend: - cd backend && npm run dev - tests: panes: - npm run test:watch
09 // Claude Code & TUI Popups

Floating overlays for TUI tools

display-popup (tmux 3.2+) creates floating overlay windows. The key insight for stateful tools like Claude Code: back the popup with a hidden tmux session so closing the popup merely detaches, keeping the process alive.

Ephemeral popups (stateless tools)

tmux.conf — quick overlays
bind g display-popup -E -w 80% -h 80% -d "#{pane_current_path}" lazygit bind d display-popup -E -w 80% -h 80% lazydocker bind b display-popup -E -w 80% -h 80% btop

Persistent Claude Code popup

Creates a unique background session per project directory. Dismiss the popup, summon it again — Claude is right where you left off.

tmux.conf — Claude Code
bind -r y run-shell '\ SESSION="claude-$(echo #{pane_current_path} | md5sum | cut -c1-8)"; \ tmux has-session -t "$SESSION" 2>/dev/null || \ tmux new-session -d -s "$SESSION" -c "#{pane_current_path}" "claude"; \ tmux display-popup -w80% -h80% -E "tmux attach-session -t $SESSION"'

Persistent popup shell (general pattern)

toggle popup
bind -n M-3 if-shell -F '#{==:#{session_name},popup}' { detach-client } { display-popup -d "#{pane_current_path}" -xC -yC -w 80% -h 75% -E \ 'tmux attach-session -t popup || tmux new-session -s popup' }
Claude Code agent teams: Set "teammateMode": "tmux" in ~/.claude/settings.json and Claude automatically splits panes for each teammate agent. Claude can also read other panes via tmux capture-pane — run a dev server in one pane and tell Claude to monitor it.
10 // Gotchas & Fixes

Common pitfalls

Nested tmux sessions (SSH)

The F12 toggle pattern disables all outer keybindings, passing everything through to the inner session. Status bar dims to show outer is disabled.

tmux.conf — nested session toggle
bind -T root F12 \ set prefix None \;\ set key-table off \;\ set status-style "fg=colour245,bg=colour238" \;\ if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\ refresh-client -S bind -T off F12 \ set -u prefix \;\ set -u key-table \;\ set -u status-style \;\ refresh-client -S

Stale SSH_AUTH_SOCK

After reattaching over a new SSH connection, existing panes still reference the dead socket. Quick fix:

~/.bashrc or ~/.zshrc
# Run 'fixssh' in any pane after reattaching fixssh() { eval $(tmux show-env -s | grep '^SSH_'); } # Permanent fix: symlink in ~/.ssh/rc if test "$SSH_AUTH_SOCK"; then ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock fi # Then in tmux.conf: # setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock

Terminal compatibility matrix

Alacritty — keep native TERM, has undercurl
Kitty — add terminal-features xterm-kitty:RGB
iTerm2 — enable "Apps may access clipboard"
WezTerm — works out of the box
Ghostty — works out of the box
GNOME Terminal — no OSC 52, use xclip
macOS Terminal.app — no OSC 52, no undercurl

Portable config with version guards

conditional config
# Version-dependent features %if "#{>=:#{version},3.2}" set -as terminal-features ",xterm-256color:RGB" %else set -ag terminal-overrides ",xterm-256color:Tc" %endif # Remote-specific config if-shell 'test -n "$SSH_CLIENT"' \ 'source-file ~/.tmux/tmux.remote.conf' # Use -q to suppress errors for missing options set -gq allow-passthrough on
11 // Reference

Searchable cheatsheet

All bindings assume prefix = C-a and the custom config from this guide. Type to filter.

Sessions

New session (attach-or-create)tmux new -As name
Detach from sessionprefix + d
Session tree chooserprefix + s
Next / previous sessionprefix + ) / (
Last session (toggle)prefix + L
Rename sessionprefix + $
Kill sessiontmux kill-session -t name
fzf sessionizerprefix + f

Windows

New windowprefix + c
Rename windowprefix + ,
Next / previous windowprefix + n / p
Jump to window Nprefix + 0-9
Last window (toggle)prefix + l
Window chooserprefix + w
Reorder left / rightprefix + Shift-← / →
Close windowprefix + &

Panes

Split vertical (side-by-side)prefix + |
Split horizontal (stacked)prefix + -
Navigate h/j/k/lprefix + h j k l
Resize (repeatable)prefix + H J K L
Zoom toggle (fullscreen)prefix + z
Break pane to windowprefix + !
Join pane from windowprefix + J
Send pane to windowprefix + B
Swap panesprefix + { / }
Cycle layoutsprefix + Space
Show pane numbersprefix + q
Last active paneprefix + ;
Toggle pane syncprefix + S
Close paneprefix + x

Copy Mode (vi)

Enter copy modeprefix + [
Begin selectionv
Line selectionV
Rectangle selectionCtrl-v
Yank and exity
Search backward / forward? / /
Half-page scroll up / downCtrl-u / Ctrl-d
Quick backward searchprefix + /

Popups & Tools

lazygit popupprefix + g
Claude Code popupprefix + y
lazydocker popupprefix + d
btop popupprefix + b
Toggle popup shellAlt + 3

Plugins

Install plugins (TPM)prefix + I
Update pluginsprefix + U
Save session (resurrect)prefix + Ctrl-s
Restore sessionprefix + Ctrl-r
tmux-thumbs quick copyprefix + Space
Reload configprefix + r
Toggle mouseprefix + m
Nested session toggleF12