Properties3
TypePractice#ai-generated: content written by an agent.
Note createdAug 8, 2026
Last updatedAug 9, 2026

A cheatsheet for the tmux config I keep in my dotfiles. The arrangement: Ghostty is a display surface only, tmux owns every session, window and pane, and rootshell on the iPhone attaches to the same sessions over tmux -CC. The point is that Claude Code agents keep running when I close the laptop lid or walk away.

Config lives in dotfiles/tmux/, linked to ~/.config/tmux. The launcher is a fish function in dotfiles/fish/functions/dvicente/.

Starting a workspace

Two long-lived sessions: work (~/Projects) and personal (~/Projects/Personal). The session is the unit of attachment, which is why there are exactly two — a tmux client attaches to one session, so two pinned windows means two sessions.

tmux-connect personal      # attach here, or switch if already inside tmux
tmux-connect work
tmux-connect -w work       # open a new Ghostty window pinned to it

Idempotent: it always does new-session -A, so re-running attaches instead of forking a duplicate. The session is created detached first, so it exists even if the window never opens — which is what lets the phone attach to something I never opened on the Mac.

Keys

Two leaders, one per device:

DevicePressBytetmux slot
Mac (Ghostty)cmd+;0x1cprefix = Ctrl-\
iPhone (rootshell)Ctrl then b0x02prefix2 = Ctrl-b

macOS never sends Cmd to the terminal, so tmux can’t bind cmd+; at all — Ghostty catches the chord and injects the byte. Quick terminal moved to cmd+' to free it up.

Ctrl-b is there for the phone: rootshell’s on-screen keys are a fixed set and its keybind config isn’t reachable on iPhone, so the prefix has to be something the toolbar can already type. It’s also tmux’s stock prefix, so every doc I read applies unmodified. The cost is losing Ctrl-b (backward-char) in fish everywhere, which is just arrow-left.

Deliberately not Ctrl-Space: that’s NUL (\x00), which iOS swallows as the system input-language switcher, and it’s the one byte with known delivery flakiness inside a text: action. Control characters are the key’s ASCII code masked to five bits — b is 0x62, and 0x62 & 0x1F is 0x02.

KeyDoes
prefix | / -split right / down, in the current pane’s directory
prefix h j k lmove between panes
prefix H J K Lresize, repeatable
prefix zzoom pane
prefix xkill pane
prefix tnew window — t for tab (c, tmux’s default, also works)
alt+1..5jump straight to window N, no prefix
prefix Tablast window
prefix n / pnext / previous window
prefix ssession tree
prefix Ctrl-\last session (i.e. cmd+; twice)
prefix Entercopy mode (v select, y yank, q quit)
prefix rreload config

Nothing in Ghostty handles splits or tabs any more — cmd+d, cmd+t, cmd+1..9, cmd+hjkl and friends are all unbound so the keystrokes fall through. Window-level keys (cmd+n, cmd+w, cmd+q) stay with macOS, which still owns geometry and Spaces.

Layout discipline

  • Session = context (work / personal). The Ghostty-window boundary.
  • Window = one per repo. Cheap, named, alt+N addressable.
  • Pane = concurrent processes inside one repo — agent, shell, logs.

Rule: windows when the work belongs together, panes when I need to see them at once. Panes shrink the agent’s usable width, which visibly degrades Claude Code’s rendering, so a wall of tiny panes costs real throughput.

Connecting from the phone

Attach with a grouped session, never a plain attach:

tmux -CC new-session -t work

Grouped sessions share windows but size independently, so the phone can’t squeeze the Mac client down to iPhone dimensions.

Detach, don’t close the tab. This is the one that bit me. Grouped sessions share windows, and in control mode closing a native tab sends kill-window rather than detaching — which deletes that window on the Mac too. If it was the last window the session dies, and the whole server goes with it. Verified directly:

kill-session on the grouped session  → base session survives   ✅
kill-window  on the grouped session  → "no server running"     ❌

exit-empty off and destroy-unattached off are in the config as backstops, but they only stop the cascade, not the loss of that window.

Things that silently don’t work

#{@my-var} in option values. tmux expands user options only in command arguments that take a format — never in a target (-t) and never in a built-in option’s value. So set -g history-limit "#{@lines}" quietly leaves the default, and switch-client -t "#{@workspace}" fails at runtime with can't find session. Workspace names live in the fish function as real variables instead.

terminal-overrides matching the wrong TERM. The pattern matches the outer terminal, which Ghostty sets to xterm-ghostty. A *256col* pattern never matches it, so truecolor dies without a word. Needs an explicit xterm-ghostty:RGB entry. And overrides are negotiated at client attach time — prefix + r won’t apply them, I have to detach and reattach.

Shift+Enter collapsing to Enter. Without extended-keys on plus terminal-features xterm*:extkeys, tmux can’t tell them apart and multi-line prompting in Claude Code breaks. This is the most common “Claude Code feels worse in tmux” complaint.

Notifications vanishing. tmux intercepts the OSC sequences Claude uses, so desktop-notifications silently does nothing without allow-passthrough on.

Flicker. Claude Code needs DEC mode 2026 synchronized output — terminal-features *:sync.

cmd+h. Also a system Hide-Window shortcut. Has to be cleared in Settings → Keyboard → Keyboard Shortcuts before tmux ever sees it.

What tmux costs me

Layering a second terminal emulator inside Ghostty is not free, and it’s worth remembering what I gave up:

  • Native scrollback and Cmd+F search — tmux owns history now, so Ghostty’s scrollbar only sees the visible viewport. Copy mode or nothing.
  • OSC 7 working-directory reporting — tmux consumes it, so cmd+n opens in a stale directory.
  • Prompt marking / jump-to-prompt — unreliable inside tmux even with shell integration forced.
  • Mouse ownership — scroll and drag-select are the same event stream. mouse on gives coherent scrolling in TUIs at the cost of plain drag-select; Shift+drag gets selection back.

The trade is worth it purely for persistence: Ghostty splits die with the process, and agent sessions that survive a reboot are the entire reason for this setup.

On waiting for Ghostty 1.4

Ghostty is building native tmux -CC control mode, which would give panes back to Ghostty and undo most of the list above. As of August 2026 it is not close: the parsing exists in src/terminal/tmux/ but is not wired to the GUI, the tracking issue has no assignee or milestone, the architecture PR was auto-closed by the vouch bot, and the active contributor’s branch last moved in April. I should not plan around it.

Related

  • Claude Code — the reason any of this needs to survive a disconnect
  • Tailscale — how the phone reaches the laptop when it’s not on my network
  • Ghostty, fish