tmux¶
Managed tmux config lives under ~/.config/tmux/, with a shim at ~/.tmux.conf that sources the XDG entry point.
Source layout (in this repo)¶
| Source | Deployed | Purpose |
|---|---|---|
dot_tmux.conf |
~/.tmux.conf |
one-line shim |
dot_config/tmux/tmux.conf |
~/.config/tmux/tmux.conf |
entry point, picks theme |
dot_config/tmux/common.conf |
— | plugins + theme-agnostic options |
dot_config/tmux/keybindings.conf |
— | all bind-keys and popup menu |
dot_config/tmux/theme.catppuccin.conf |
— | default theme (top status bar) |
dot_config/tmux/theme.tmux2k.conf |
— | alternative theme (bottom status bar) |
dot_config/tmux/executable_responsive.sh |
~/.config/tmux/responsive.sh |
responsive status bar for Catppuccin |
This setup is tuned for coding-agent and Neovim workflows:
- native popup menu on
prefix + Space; script-driven (~/.config/tmux/menu.sh) with submenus and height-aware tier trimming — see keybindings.md - fuzzy keybinding search on
prefix + ?(tmux-fzf, replaces the built-in list-keys dump) - markdown cheatsheet via
prefix + Space→?(rendered withglow) - floating scratchpad on
prefix + F(tmux-floax, persistent), one-shot popup shell onprefix + \``, lazygit popup onprefix + G` - Catppuccin (default, top status bar) or tmux2k (bottom) — switchable at runtime
- Catppuccin status bar is responsive — modules adapt to terminal width (mobile-friendly)
- vim-style pane navigation and copy mode
- URL opening via fzf (
prefix + u) and tmux-open in copy mode - capture pane to clipboard helpers
- sesh integration for session picking
- live coding-agent pane discovery plus quota wakeup scheduling (
prefix + a,prefix + M-a) extended-keyswithcsi-uso keys likeCtrl+/reach Neovim inside tmux- OSC 52 clipboard and OSC passthrough enabled
- macOS terminal must send Option as Meta for
M-bindings — see ghostty.md
In this folder¶
- keybindings.md — all keybindings and the popup menu
- themes.md — Catppuccin / tmux2k selection, switching, troubleshooting
- vim.md — tmux × Vim/Neovim notes and external resources
First-time setup¶
After chezmoi apply, start tmux and run prefix + I once to let TPM install the plugins. The theme files also auto-clone their plugin on first source as a safety net, but TPM is the canonical installer.
Version requirement: tmux >= 3.3¶
The popup menu (bound to prefix + Space) is generated by ~/.config/tmux/menu.sh and uses display-menu -x R -y P. Two distinct failure modes drove the current design:
- Position clamping (tmux 3.3+ required). On tmux 3.2a (shipped by Ubuntu 22.04 apt) the menu is positioned past the terminal's right edge and silently suppressed. tmux 3.3 introduced position clamping and arithmetic in
-x/-y, so the menu renders correctly. The man page explicitly states "If the menu is too large to fit on the terminal, it is not displayed." - Menu-too-tall is a separate failure mode.
display-menudoes not paginate. Even on tmux 3.6+, a 50-row flat menu silently fails to open on terminals shorter than ~50 lines (mobile SSH, half-screen splits, etc.) — no error, no log entry. Fixed by the script-driven, height-tiered approach: the script reads#{client_height}and emits one of three tier sets so the top menu never exceeds ~14 rows. Lower-frequency items moved into submenus. Full debugging story:pitfalls/tmux-display-menu-silent-fail.md.
The ansible devtools role now checks the tmux version and, when it is below 3.3, upgrades automatically:
- Linuxbrew present →
brew install tmux(latest stable). - No Linuxbrew, x86_64 Linux → download
nelsonenzo/tmux-appimage, extract to~/.local/share/tmux-appimage/squashfs-root/(no FUSE needed), and install a shim at~/.local/bin/tmuxthat exec's the bundled binary.~/.local/binsits ahead of/usr/binin PATH so the newer binary wins. - No Linuxbrew, non-x86_64 (e.g. armv7l, aarch64 servers without brew) → ansible prints a warning; build from source or enable Linuxbrew.
Switching a running server to the new binary¶
After an upgrade, existing tmux servers keep running the old binary (a live process doesn't re-exec on PATH change). Clients attaching to the same socket also stay on the old version. To switch over:
# lose current sessions and suppress tmux-continuum auto-restore
tmux-kill-clean # or: ~/.config/tmux/kill-server-clean.sh
# or: start the new server on a new socket and migrate sessions manually
tmux -L new new-session -s work
Verify with tmux -V (binary version) and, inside tmux, #{version} in a format string (server version).
Terminfo (missing or unsuitable terminal)¶
If tmux attach fails with missing or unsuitable terminal: <name>, the host is missing the terminfo entry for whatever $TERM your outer terminal advertises (common on minimal Ubuntu installs, Docker images, and freshly provisioned servers — base ncurses ships only xterm, vt100, linux, …).
On Debian/Ubuntu the devtools role now installs ncurses-term alongside the tmux baseline, which adds entries for tmux-256color, alacritty, ghostty, rxvt-unicode-256color, screen-256color, and friends. macOS ships the extended catalog by default.
Fallbacks if you can't install the package (e.g. noRoot mode on a locked-down host):
# quick test — pick a terminfo that's already present
TERM=xterm-256color tmux a
# user-local install: copy your local terminfo entry over SSH
infocmp -x | ssh remote -- tic -x -
# lands in ~/.terminfo/, no sudo needed
OSC 52 Clipboard (SSH-friendly yank)¶
Full cross-layer explainer (terminal ↔ tmux ↔ Neovim ↔ x CLI): see clipboard.md. Summary of what tmux contributes:
set-clipboard on + allow-passthrough on in common.conf let tmux relay OSC 52 escape sequences from inner TUIs to the outer terminal emulator, so yanks reach the local machine's clipboard even over SSH. The same file declares the clipboard terminal-feature for xterm*, ghostty*, and alacritty* so tmux doesn't need a Ms entry in terminfo to emit OSC 52.
The capture-pane helpers (prefix + y / Y / C-y in keybindings.conf) pipe through tmux load-buffer -w - rather than pbcopy/xclip, so they also work over SSH (the -w flag tells tmux to forward the buffer via OSC 52 alongside the paste buffer).
The Neovim side is paired in dot_config/nvim/lua/config/options.lua: when SSH_CONNECTION/SSH_TTY is set, vim.g.clipboard is overridden to vim.ui.clipboard.osc52 (builtin since Neovim 0.10). Local sessions keep the default pbcopy/xclip provider so paste still works.
Verify it's wired up¶
# tmux should report clipboard support for the current client
tmux info | grep -Ei 'clipboard|Ms:'
# look for: clipboard: true, and an Ms entry that's NOT [missing]
# inside Neovim
:checkhealth provider.clipboard
# expect the "OSC 52" provider to be active under SSH
If tmux info still shows Ms: [missing] and yanks fail after editing common.conf, a running tmux server keeps its old capability table. Do tmux kill-server (loses sessions — tmux-resurrect can restore them) and attach fresh.
Terminal support:
| Terminal | OSC 52 write | OSC 52 read |
|---|---|---|
| Ghostty / cmux | ✅ clipboard-write = allow (explicit in dot_config/ghostty/config) |
prompts (clipboard-read = ask) |
| Alacritty | ✅ native | ❌ not supported |
| iTerm2 | ✅ | requires opt-in in Preferences |
| kitty / WezTerm | ✅ | ✅ |
Paste from the system clipboard over OSC 52 is therefore best-effort; yank is the reliable direction. If remote paste matters more than remote yank, use SSH's LocalForward/ForwardAgent workflows or a dedicated clipboard bridge (e.g. lemonade).
Scrollback & Coding Agents¶
Streaming TUIs (Claude Code, OpenCode, Codex) repaint the same screen region via ANSI sequences. On the alternate screen (vim, htop, Claude Code's interactive session) tmux's scrollback is unaffected — alt-screen has its own buffer. On the main screen (some agents, live progress bars, cargo build with ascii status), every frame can be pushed into history by default, producing ghost/duplicated lines that make scrolling back look broken compared to a native terminal.
Two settings and one workflow keep this clean:
set -g scroll-on-clear off(incommon.conf) — discards pre-clear contents instead of pushing them into history on full-screen clear (ED). Defaults toonsince tmux 3.3.history-limit 50000— already set; plenty even for a full day of agent sessions.- Freeze before scrolling:
prefix + [enters copy-mode, which snapshots the current frame. Scroll the wheel or useC-u/C-dwithout the UI continuing to repaint under you.qexits.
For a pitfall-level description of why this can't be "fixed" further (ANSI redraw + linear scrollback is fundamentally lossy), see pitfalls/tmux-scrollback-tui-repaint-ghosting.md.
OSC 133 Command-Boundary Navigation (Warp-style)¶
dot_config/zsh/tools/02_shell_integration.zsh emits OSC 133 prompt markers (A = prompt start, C = command output start, D = output end + exit code) via add-zsh-hook precmd / preexec. Chains cleanly with starship / zsh-vi-mode / oh-my-zsh — no precmd clobbering. tmux 3.4+ parses them and exposes navigation in copy-mode plus a one-press "copy last output":
| Key | Context | Action |
|---|---|---|
{ / } |
copy-mode | Jump to previous / next prompt line |
Alt+[ / Alt+] |
copy-mode | Jump to previous / next command output start (line after prompt) |
prefix + M-y |
top-level | Copy the last command's output to clipboard (Warp-style one-press) |
prefix + M-i |
top-level | Copy the last command's input line (prompt + typed command) to clipboard |
prefix + M-i copies the whole prompt line because we intentionally don't emit the B marker (see the note at the bottom of 02_shell_integration.zsh) — so the clipboard looks like ❯ echo hi rather than just echo hi. Trim in your paste target if needed.
Shell-level equivalents live in 03_tmux_capture.zsh — runnable from the prompt, pipe-friendly (cpout | grep ERROR), and copy to clipboard as a side effect. Each takes an optional positional N for "Nth-latest command" lookback (default 1):
| Command | Equivalent to | What it copies |
|---|---|---|
cpout [N] |
prefix + M-y (N=1) |
Nth-latest command's output only |
cpcmd [N] |
prefix + M-i (N=1) |
Nth-latest command's input line |
cpblock [N] |
— | Nth-latest command's full block (prompt + input + output) |
Reviewing past commands with AI agents¶
The AI wrappers — aifix / aiexplain + the aiblock TUI — pipe a captured block into a coding-agent CLI (Claude Code / OpenCode / Codex / Cursor Agent) in non-interactive advisory mode. Model defaults, prettify/metadata/spinner toggles, standalone setup (no chezmoi), and troubleshooting all live in the dedicated user guide:
docs/tools/aicapture.md— single-page walkthrough, share this link with anyone who wants to try the toolsdocs/this_repo/instant-llm-fix-prior-art.md— where this layer fits againstthefuck, Warp,wut/tmuxai,butterfish,atuin, and the OSC 133 terminals
Opt out of OSC 133 per shell: export DISABLE_OSC133=1 before starting zsh. The copy-mode bindings become no-ops automatically when markers are absent — including in shells that pre-date the chezmoi apply that added the hook (reloading tmux config doesn't re-source zsh; run exec zsh in the affected pane or open a new one). Symptom of a pre-hook shell: prefix + M-y / M-i flash their success message but paste is empty. Verify with echo $precmd_functions | tr ' ' '\n' | grep osc133.
Reload Config¶
Most changes do not need a server restart.
Or, inside tmux: prefix + R (shows a confirmation message after reloading).
If a change still behaves wrong, try a new pane/window first. Only tmux kill-server when a terminal-capability or plugin-init issue clearly survives a reload — e.g. switching themes without leftover styling.
Exit vs Detach¶
prefix + d— detach the current client, leaving the session runningexit— close the current shell; when the last pane exits, the session endsprefix + :thenkill-session— kill the current session immediatelytmux-kill-cleanorprefix + M-Q— kill every session, stop the server, and clear tmux-resurrect'slastpointer so this intentional quit does not auto-restoretmux kill-server— raw server kill; with tmux-continuum enabled, the next empty start may restore the previous snapshot
detach-on-destroy off is set, so when you kill a session tmux switches to another one instead of detaching if other sessions exist.
Prefix Key¶
Default: Ctrl + b. Examples in the sibling docs assume prefix = Ctrl + b.
Plugins¶
| Plugin | Purpose |
|---|---|
tmux-resurrect |
Save/restore sessions across tmux restarts |
tmux-continuum |
Automatic session saving |
tmux-floax |
Floating scratch pane (prefix+F toggle, prefix+P menu) |
tmux-fzf |
Fuzzy picker for keybindings/sessions/windows/panes (prefix+? rebound from list-keys) |
tmux-fzf-url |
prefix+u opens fzf popup with all URLs in pane |
tmux-open |
In copy-mode: o opens selection, C-o in editor, S search |
catppuccin/tmux |
Status bar theme (mocha, top bar) — default |
tmux2k |
Status bar theme (onedark, bottom bar) — alternative |
Managed by TPM; prefix + I to install, prefix + U to update.
Verify current config¶
ls ~/.config/tmux/
tmux display-message -p '#{@theme_variant}'
tmux list-keys -T prefix
tmux show-options -s | rg 'extended-keys|extended-keys-format|default-terminal'
tmux show-options -g | rg 'status|focus-events'
Related Docs¶
- Sesh
- Starship
- XDG Base Directory — why configs live under
~/.config/tmux/rather than~/.tmux.conf