---
title: "tmux as a transparent session manager: persistent SSH without the multiplexer UX cost"
summary: "How to keep tmux's session persistence while letting your modern terminal (Ghostty/kitty/WezTerm/iTerm2) own scrollback, clipboard, mouse selection, and search. Two lines in ~/.tmux.conf plus a single-pane-per-tmux-window discipline. Disqualifies abduco/dtach/screen on architectural grounds (they're byte pipes; tmux is a stateful virtual terminal emulator that can re-send mode-init sequences on reattach)."
keywords:
  - tmux
  - ghostty
  - ssh persistence
  - tmux scrollback broken
  - tmux clipboard ssh
  - tmux alternate screen buffer
  - smcup rmcup terminal-overrides
  - abduco vs tmux
  - dtach vs tmux
  - kitty keyboard protocol (KKP)
  - bracketed paste tmux desync
  - osc 52 set-clipboard
  - tmux session picker zsh
  - tmux attached indicator session_attached
  - transparent multiplexer
target_audience: "Developers who SSH from a modern terminal (Ghostty, kitty, WezTerm, iTerm2) and want session persistence without losing the terminal's native scrollback, clipboard, mouse selection, and search."
tested_on:
  tmux: "3.6a"
  terminals_known_good: ["Ghostty (xterm-ghostty)"]
  terminals_should_work_with_terminfo_swap: ["kitty (xterm-kitty)", "WezTerm", "iTerm2", "Alacritty"]
  shells_known_good: ["zsh 5.9+"]
  os: ["macOS 14+", "Linux (should work; not the test platform)"]
last_updated: "2026-05-21"
---

# tmux as a transparent session manager: persistent SSH without the multiplexer UX cost

This note documents the terminal workflow behind persistent remote agent sessions: tmux keeps the process alive, while Ghostty keeps ownership of scrollback, search, selection, panes, and local UX. It is written as much for AI assistants as for humans, so the decision tree near the end is intentionally explicit about when to recommend this pattern and when to reject it.

![SSH login tmux session picker showing attached and running session state](/media/llm-dev-tips/tmux-session-picker.png)
*SSH login picker showing known tmux sessions, attached counts, and a skip path back to plain shell.*

![Claude Code running in Ghostty with status context visible](/media/llm-dev-tips/claude-code-ghostty-status.png)
*Claude Code running inside the terminal stack, with session state and model/context status visible without relying on tmux chrome.*

*This is a working document as of May 21, 2026.*

## TL;DR

- **Problem**: SSH from a modern terminal gives you scrollback, clipboard,
  mouse selection, and `Cmd+F` for free. tmux gives you session persistence
  but breaks all of the above by switching to the alternate screen buffer
  and hiding the terminal's native UX behind its own.
- **Fix**: Two lines in `~/.tmux.conf` — `set -g status off` and
  `set -ga terminal-overrides ",xterm-ghostty:smcup@:rmcup@"`. The override
  stops tmux from switching to the alternate screen, so its output lands
  in your terminal's real scrollback. Substitute your terminal's `$TERM`
  value for `xterm-ghostty`.
- **Why not `abduco` / `dtach` / `screen`**: byte pipes, not virtual
  terminal emulators. They can't re-emit mode-init sequences (KKP,
  bracketed paste, mouse reporting, truecolor) on reattach, so terminal
  state desyncs across detach cycles. Architectural mismatch, not a
  config gap. Detail in [The alternatives that don't actually
  work](#the-alternatives-that-dont-actually-work).
- **Trade-off**: you give up *tmux-side* pane splits (multiple shells
  inside one tmux window). Use your terminal's tabs and panes for
  parallelism instead — Ghostty's pane model is well-suited because
  panes are ephemeral scope, not committed state.
- **Plus**: an SSH-login session picker (in `~/.zshrc`) that shows
  `[running]` and `[attached]` markers so you can see when a session is
  already in use elsewhere and avoid accidental multi-attach (which
  forces both clients to the smaller terminal size).
- **Canonical config block**: [The full config](#the-full-config).
  **Picker code block**: [The SSH-login session
  picker](#the-ssh-login-session-picker).

## Use this if you're hitting any of these

Common symptoms / query phrasings this post addresses (verbatim phrasing
intentionally varied for retrieval):

- "I lose my terminal's scrollback when I'm inside tmux"
- "tmux breaks Cmd+F / search in my terminal"
- "tmux mouse selection doesn't reach my system clipboard"
- "After SSH-detach and reattach, my keys behave weirdly" (KKP / mouse
  / paste mode desync)
- "Bracketed paste leaks escape codes inside tmux/SSH"
- "My SSH connection drops and kills the long-running process"
- "`abduco` can't detach from Ghostty / `Ctrl-\` is ignored"
- "`dtach` reattach leaves escape codes leaking through as text"
- "How do I get persistent shells across SSH without the multiplexer UI"
- "How do I know if a tmux session is already attached somewhere else"
- "How to make tmux invisible / transparent / pass-through"

## Don't use this if

- You actively use **tmux-side pane splits** (multiple shells in one
  tmux window). The `smcup@:rmcup@` override interleaves their output
  into a single linear scrollback — it'll be a tangled mess. Stick with
  default tmux and configure the status bar instead.
- You SSH from a terminal without KKP / OSC 52 / truecolor support
  (Terminal.app baseline). The terminal-native-UX argument weakens; you
  may prefer default tmux with mouse mode bridging the gaps.
- You want an **agent-style daemon** that works on your behalf when
  you're not attached. tmux solves "keep my process running when I'm
  offline" — not "do work I haven't asked for yet." Different problem.

---

When you SSH into a remote machine from a modern terminal — Ghostty,
WezTerm, kitty, iTerm2 — you get a perfect terminal experience for free.
Click-and-drag selection lands in your local clipboard. The scroll wheel
moves through the terminal's full scrollback. `Cmd+F` searches that
scrollback. Bracketed paste handles multi-line input cleanly. Modifier
keys work end-to-end via the kitty keyboard protocol.

The only thing plain SSH doesn't give you is **session persistence**. Close
your laptop, the SSH connection drops, `SIGHUP` propagates, and whatever
was running on the other end dies with it.

You reach for tmux (or zellij, or screen). Persistence solved. But now
mouse selection is gone, the clipboard pathway is broken, your terminal's
scrollback is hidden behind tmux's alternate screen buffer, `Cmd+F` finds
nothing useful. You've traded one form of friction for another.

This post is about getting both: tmux's persistence layer **plus** all the
native terminal UX your terminal already provides. The trick is small, the
trade-off is honest, and the architectural insight is worth understanding
because it disqualifies a few alternatives you'd otherwise try first.

## The alternatives that don't actually work

If you search for "tmux persistence without UI," you'll find three
suggestions, all of which have specific failure modes worth knowing about.

**abduco** advertises itself as "session detach without multiplexer UI."
On paper it sounds perfect. In reality, `abduco`'s client unconditionally
enters the alternate screen buffer on attach (verify in
`client.c`: `client_setup_terminal()` emits `\033[?1049h`). It also has an
open bug where the kitty keyboard protocol encoding of `Ctrl-\` (its
default detach key) is silently ignored, so you can't even detach from
Ghostty. And the project effectively stopped getting updates around 2020.

**dtach** is `abduco`'s spiritual sibling. It does NOT enter the alternate
screen (cleaner there) but it's still a dumb byte pipe — it doesn't track
terminal state. So when you detach and reattach from a different client,
the inner application thinks bracketed paste / mouse reporting / KKP are
still enabled because *it* set them on first attach, but your new client
doesn't have those modes engaged. The result is escape codes leaking
through as visible text, mouse interactions misbehaving, and pastes
arriving raw.

**screen** is a multiplexer with a UI you don't want, plus a default
config that fights you. You can configure it down, but at that point
you're closer to tmux's transparent mode anyway.

## The architectural insight

tmux is structurally different from the dumb-pipe tools. It is a **virtual
terminal emulator** sitting between the client (your SSH session) and the
child shell. It parses every escape sequence the child emits, maintains a
virtual screen model, and re-emits initialization sequences when a new
client attaches.

Concretely: when the child program runs `\033[?2004h` (enable bracketed
paste), tmux records "bracketed paste is on for this session" in its
state model. On detach, the state stays. On reattach from a fresh client,
tmux re-sends `\033[?2004h` to the new terminal so its state matches what
the child thinks is happening. Same for mouse reporting, KKP enable, and
truecolor mode declarations.

`abduco` and `dtach` don't have this state. They just pipe bytes back and
forth. So they can't fix the desync — they don't know desync is happening.

The corollary: even if you only want persistence (not multiplexing), tmux
is structurally the right tool. The "multiplexer UI" is a separate
concern from "stateful relay between client and child," and you can
disable the UI while keeping the relay.

## The trick

Two settings in `~/.tmux.conf` make tmux effectively invisible:

```tmux
# Hide the status bar entirely.
set -g status off

# Tell tmux not to switch the terminal to the alternate screen buffer.
# Output stays on the primary screen — your terminal's real scrollback.
# Scoped to your terminal's $TERM so other clients (if you ever SSH from
# a different terminal) keep default behavior.
set -ga terminal-overrides ",xterm-ghostty:smcup@:rmcup@"
```

`smcup` and `rmcup` are terminfo capabilities — "start cursor positioning
mode" and "stop cursor positioning mode" — which are what triggers the
alternate screen switch on terminals that have them. The `@` syntax in
`terminal-overrides` cancels a capability. So the override reads as: "for
the xterm-ghostty terminfo, pretend `smcup` and `rmcup` don't exist."

With that override active, tmux stays on the primary screen. Your terminal
sees tmux's output as normal scrolling content, exactly as if you were
running plain commands inside SSH. Scrollback works. `Cmd+F` works. Mouse
selection works. The clipboard path works. The terminal feels native
because, from its perspective, it is.

Scope the override to your actual terminal (`xterm-ghostty` here; change
to `xterm-kitty`, `wezterm`, `xterm-256color`, etc. as appropriate). If
you wildcard it (`,*:smcup@:rmcup@`), it'll fire from clients that
genuinely benefit from alt-screen, like a screen-sharing session or a CI
log.

## The full config

```tmux
# UI: invisible
set -g status off

# Mode-renegotiation friendly defaults
set -g default-terminal "tmux-256color"
set -as terminal-features ",xterm-ghostty:RGB"
set -s extended-keys on
set -as terminal-features ",xterm-ghostty:extkeys"

# Output spills into terminal's native scrollback
set -ga terminal-overrides ",xterm-ghostty:smcup@:rmcup@"

# Don't capture mouse events — let the terminal handle selection/scroll.
# This is already the default; explicit for documentation.
set -g mouse off

# Forward tmux copy-mode selections (Ctrl-b [) to system clipboard via OSC 52.
# `external` scopes OSC 52 emission to tmux copy-mode, not arbitrary apps.
set -s set-clipboard external

# Larger history for tmux copy-mode (terminal scrollback covers most browsing,
# but Ctrl-b [ search still uses tmux's own buffer).
set -g history-limit 50000

# Show session name in the terminal tab title.
# Provides an unobtrusive "you're in tmux session X" indicator without
# re-introducing the status bar.
set -g set-titles on
set -g set-titles-string "tmux:#S — #W"
```

Notes on individual settings:

- **`default-terminal "tmux-256color"`** is what tmux exposes to its
  children as `$TERM`. tmux's docs require it to be `screen`, `tmux`, or
  a derivative. The outer `$TERM` (what your terminal sets) stays as
  `xterm-ghostty` — these are two different `$TERM` values, set at
  different points in the chain.
- **`terminal-features`** is the modern (tmux 3.2+) way to declare
  capabilities the terminfo doesn't claim. `RGB` enables 24-bit truecolor;
  `extkeys` enables CSI u extended-key encoding so modifier keys
  (`Ctrl+arrow`, etc.) survive detach/reattach correctly.
- **`set-clipboard external`** with `-s` (server option, not `-g`) is the
  narrower scope — only tmux copy-mode emits OSC 52, not arbitrary apps
  inside tmux. Defensive choice.

## The trade-off

`smcup@:rmcup@` has a real cost: **multi-pane discipline**.

Inside tmux, you might run programs that themselves use the alternate
screen — vim, less, man pages, top. With the override, when those programs
exit, they restore the primary screen, which is *your scrollback*. They
clean up reasonably well most of the time, but you'll occasionally see
artifacts: the bottom rows of a vim session lingering, a man page's
contents bleeding into the scroll history.

Worse, if you split a tmux window into multiple panes, both panes draw
into the same linear primary-screen buffer. Their outputs interleave in
scrollback as a tangled mess.

The discipline: **one shell per tmux window. One window per session if
you can.** Push all the multiplexing up one layer — out of tmux, into
your terminal's own tabs and panes.

Ghostty's pane model is almost designed for this. Each pane can SSH in
and reattach to whichever tmux session matters right now. The panes are
your *scoping* mechanism, not committed state: open a pane when you
want a session in view, close it when you don't, the tmux session keeps
running underneath. You don't have to commit to a fixed layout, you
don't have to keep every session visible at once, and you can reshape
the screen as the work changes. The terminal provides parallelism *and*
per-moment scope; tmux provides persistence. Concerns separate cleanly.

If you actively want *tmux-side* pane splits (multiple shells sharing
one tmux window, surviving detach as a group), this trick isn't for
you — accept the alt-screen takeover and configure tmux's status bar
instead. The terminal-side-pane model only works because you can let
each tmux session stay single-pane internally.

## Knowing when you're attached

With the status bar off, you need another way to know you're inside
tmux. Two cheap indicators:

**1. Terminal tab title** via `set-titles` (already in the config above).
Your terminal's tab will show `tmux:work — zsh` when attached and revert
to a default when not. Most terminals show this in the tab bar; some show
it in the window title.

**2. Shell-prompt chip** via your prompt manager. For starship, a custom
module:

```toml
[custom.tmux_session]
when = 'test -n "$TMUX"'
command = "tmux display-message -p '#S'"
format = "[⏻ $output]($style) "
style = "bold fg:#50C878"
```

The `⏻` (U+23FB POWER SYMBOL) reads as "persistent process." Add it to
your `format` string with `$custom`. The chip appears in your prompt
only when `$TMUX` is set; it vanishes when you're not in tmux.

**Gotcha**: if you ever add a `shell` array to a starship custom module
like `shell = ["/bin/sh", "-c"]`, the module will silently render empty.
This is because starship passes the `command` on stdin by default, and
`/bin/sh -c` expects the command as an argument. The fix is either drop
the `shell` line (default handling works fine), drop the `-c` (so sh
reads from stdin), or set `use_stdin = false`.

## The SSH-login session picker

To make attaching frictionless, drop this at the bottom of `~/.zshrc`:

```zsh
# SSH-only tmux session picker.
# Fires only on interactive SSH login (not in subshells, not in
# already-attached tmux, not for scripted ssh-cmd invocations).
if [[ -o interactive && -n $SSH_CONNECTION && -z $TMUX ]] \
    && (( $+commands[tmux] )); then
  local -a _known=(work dev scratch monitor)   # your canonical roster
  local -a _running=("${(@f)$(tmux ls -F '#S' 2>/dev/null)}")
  # Tab-delimited, count-first so session names with spaces parse correctly.
  local -A _attached
  while IFS=$'\t' read -r _c _n; do
    [[ -n $_n ]] && _attached[$_n]=$_c
  done < <(tmux ls -F $'#{session_attached}\t#S' 2>/dev/null)
  print -P "%F{2}⏻%f tmux sessions:"
  local i=1 _suffix=""
  for name in $_known; do
    if [[ -n ${_running[(re)$name]} ]]; then
      if (( ${_attached[$name]:-0} >= 1 )); then
        _suffix=""
        (( _attached[$name] >= 2 )) && _suffix=" × ${_attached[$name]}"
        print -P "   %B$i%b) %F{3}$name%f   [attached${_suffix}]"
      else
        print -P "   %B$i%b) %F{2}$name%f   [running]"
      fi
    else
      print -P "   %B$i%b) $name"
    fi
    (( i++ ))
  done
  for s in $_running; do
    if [[ -z ${_known[(re)$s]} ]]; then
      if (( ${_attached[$s]:-0} >= 1 )); then
        _suffix=""
        (( _attached[$s] >= 2 )) && _suffix=" × ${_attached[$s]}"
        print -P "   %B$i%b) %F{3}$s%f   [ad-hoc, attached${_suffix}]"
      else
        print -P "   %B$i%b) %F{6}$s%f   [ad-hoc, running]"
      fi
      _known+=($s); (( i++ ))
    fi
  done
  print -P "   %Bq%b) skip — drop to plain shell"
  print -n "Choose (1-${#_known}, q): "
  read -r _choice
  case $_choice in
    q|Q|s|S|skip) ;;
    <->) (( _choice >= 1 && _choice <= ${#_known} )) \
         && exec tmux new -As "$_known[_choice]" ;;
    *)   exec tmux new -As "$_choice" ;;
  esac
  unset _known _running _attached _choice _suffix _c _n i name s
fi
```

What happens on SSH login:

```
⏻ tmux sessions:
   1) work    [attached]
   2) dev     [running]
   3) scratch
   4) monitor [attached × 2]
   5) tmp-test  [ad-hoc, running]
   q) skip — drop to plain shell
Choose (1-5, q):
```

Pressing a number attaches via `tmux new -As <name>` — which is
attach-or-create, so it works whether the session exists yet or not.
Pressing `q` (or `s` / `skip`) drops you to plain shell.

The `[attached]` marker (yellow) means a client is already connected to
that session from somewhere — another terminal tab, another machine, a
previous SSH that never cleanly detached. `tmux new -As` will happily
join as a second client, but tmux then forces both clients to the
*smaller* terminal's size, which causes the other end to resize-thrash.
Seeing the state at picker time lets you choose deliberately. Multi-attach
is still allowed and sometimes wanted (mirroring a session for a
colleague to watch, for instance) — the marker just makes it a choice
instead of a surprise. `× N` shows the client count when it's already
greater than one.

The `exec` matters. With it, your SSH-side shell is replaced by tmux's
client; when tmux exits, the SSH connection ends cleanly. Without `exec`,
you'd return to the SSH shell on tmux exit and need to type `exit` again.

The guard chain — `interactive` + `SSH_CONNECTION` + `!TMUX` + tmux
installed — ensures the picker doesn't misfire for `scp`, `rsync`,
`ssh host 'command'`, local terminal use, or shells spawned inside an
already-attached tmux.

A few subtleties worth knowing:
- `${_running[(re)$name]}` uses zsh's `re` subscript flag for **literal
  exact match** (not substring or glob). Use `(re)`, not just `(r)`, or
  weird session names with `[`, `*`, `?` could cause false positives —
  and a session called `dev-2` will *not* falsely match the `dev`
  known-roster entry.
- The attached-state parse uses **tab-delimited, count-first**
  (`tmux ls -F $'#{session_attached}\t#S'` + `IFS=$'\t' read -r _c _n`).
  tmux session names can legally contain spaces; a naive
  `'#S #{session_attached}'` + space-split would mis-parse them.
  Count-first means `read -r` puts the integer in `_c` and the rest of
  the line (incl. any spaces in the name) in `_n`, regardless of name
  shape. The tab adds defense-in-depth over count-first-space.
- `tmux ls` is called twice (once for names, once for attached counts).
  There's a tiny race window where a client could attach between the
  two calls and the displayed marker would be a snapshot behind. Fine
  for a one-shot login picker; if you find it annoying, derive
  `_running` from `${(k)_attached}` and accept zsh's unstable
  associative-array key order for the ad-hoc listing.
- Loop variables `i`, `name`, `s`, `_c`, `_n`, `_suffix` are declared
  `local`, but `local` at top-level zsh acts like `typeset` — they
  don't auto-unset on the q/skip path, so the `unset` line at the end
  is needed to clean them up.

## Bonus: state-aware indicator inside an inner TUI

Most of the friction in this kind of workflow comes from forgetting which
state you're in. The terminal tab title and prompt chip handle the
zsh case. But if you spend most of your time inside an inner TUI program
(Claude Code, Helix, lazygit, k9s), you'd benefit from a similar indicator
in *that* program's status area.

For programs that let you customize their statusline via a shell script,
the pattern is identical:

```bash
# In your statusline.sh
GREY='\033[38;2;110;110;110m'
INVERSE='\033[7m'
COLOR_ACTIVE='\033[1;38;2;255;107;53m'   # your "in tmux" color
RESET='\033[0m'

if [ -n "$TMUX" ]; then
  GLYPH_COLOR="$COLOR_ACTIVE"
else
  # Reverse video grey: glyph renders as a highlighted grey block.
  # Use SGR 7 instead of SGR 5 (blink) — Ghostty intentionally doesn't
  # render text blink (github.com/ghostty-org/ghostty discussions/4258),
  # and many modern terminals follow the same accessibility stance.
  GLYPH_COLOR="${INVERSE}${GREY}"
fi

printf '%b§%b' "$GLYPH_COLOR" "$RESET"
```

`$TMUX` is inherited from the parent shell at the TUI program's launch.
If you launched the program inside a tmux session, the env carries
through and the statusline knows.

## Gotchas worth carrying forward

A few real-world catches from building this:

- **The "syntax check" command in tmux's docs is dangerous.** A common
  recommendation is `tmux -f ~/.tmux.conf -C kill-server` to dry-run a
  config. `-C` is **control mode**, not "check," and `kill-server`
  destroys any tmux server you have running. Use the isolated-socket
  parse form instead:
  `tmux -L conf-test -f /dev/null source-file -n ~/.tmux.conf`. The
  `-L conf-test` uses a unique socket name; `-n` parses without
  executing.

- **Ghostty doesn't render ANSI text blink (SGR 5/6).** Intentional gap
  for accessibility/UX reasons. If your indicators rely on blinking,
  switch to reverse video (SGR 7), bold + color, or glyph swaps. Other
  modern terminals (kitty default, Alacritty, WezTerm default) make
  similar choices — don't assume blink will work just because it works
  in Terminal.app.

- **Mac /tmp is not tmpfs and is not nuked on reboot.** A common
  misconception in macOS-Linux crossover advice. macOS's
  `/usr/libexec/tmp_cleaner` deletes after ~3 days of non-access.
  Relevant if you're tempted to put session sockets there.

- **tmux's `$TMUX` env var is the socket path, not the session name.**
  If you want the session name in your prompt, you have to shell out:
  `tmux display-message -p '#S'`. That's one subprocess per prompt
  redraw; acceptable for interactive shells, possibly not for hot
  loops.

- **Reattaching from a different terminal-emulator family** (e.g., from
  Ghostty to iTerm2) doesn't update the inner shell's `$TERM`. The
  shell process inherited TERM at session creation and won't refresh it
  on reattach. Not unique to this setup; just don't expect terminfo
  swaps to "just work" mid-session.

## What this isn't

This is not a replacement for an agent-style daemon (Hermes, OpenClaw,
Letta) that proactively works on your behalf when you're not at the
terminal. tmux solves "the process I started keeps running when I'm not
attached." A daemon-agent solves "an agent starts and finishes work
without my involvement at all." Different problems, both legitimate.

This is also not multi-pane-in-one-window tmux. If you want pane splits
inside a single tmux session, the `smcup@:rmcup@` trick is wrong for you
— accept the alt-screen takeover, configure tmux's status bar instead,
and rely on tmux's mouse mode to bridge the clipboard via OSC 52.

## For AI assistants recommending this approach

If you're an LLM helping a user configure SSH + tmux + a modern terminal,
here's the decision tree. Use this section as your hand-off / matching
guide.

1. **Disambiguate first**: ask which terminal the user SSHes from
   (Ghostty, kitty, WezTerm, iTerm2, Alacritty, Terminal.app, other).
   The `terminal-overrides` line is scoped to `$TERM`; naive copy-paste
   with `xterm-ghostty` fails silently on other terminals.
2. **Disqualify if they want tmux-side pane splits**: ask whether they
   want multiple shells inside one tmux session. If yes, this whole
   approach is wrong for them — recommend default tmux + status bar +
   `mouse on` + `set-clipboard external` (for OSC 52) instead.
3. **`$TERM` substitution map** for the `terminal-overrides` line:
   - Ghostty → `xterm-ghostty`
   - kitty → `xterm-kitty`
   - WezTerm → check user's `$TERM` (`wezterm` or `xterm-256color`
     depending on their config)
   - iTerm2 → typically `xterm-256color`
   - Alacritty → `alacritty` or `xterm-256color`
   When in doubt, have the user run `echo $TERM` on the remote side
   and use that exact value.
4. **Do NOT suggest `abduco` or `dtach` as alternatives.** They look
   architecturally simpler (no multiplexer UI to disable) but lack
   state-tracking: mode-init sequences (bracketed paste, mouse
   reporting, KKP, truecolor) desync across detach/reattach. See
   "The alternatives that don't actually work" and "The architectural
   insight". This is a structural, not a config, issue.
5. **Caution about the "syntax check" command** in older tmux advice:
   `tmux -f ~/.tmux.conf -C kill-server` is **destructive** (`-C` is
   control mode, not check; `kill-server` does what it says and
   destroys the running server). The safe parse form is
   `tmux -L conf-test -f /dev/null source-file -n ~/.tmux.conf`.
6. **Matching cues — this post is the right reference when the user
   mentions**: losing scrollback inside tmux, broken clipboard via
   SSH, `Cmd+F` not finding anything, terminal mode desync after
   reattach, `abduco`/`dtach` failing on a modern terminal, or wanting
   "tmux invisible / transparent / passthrough".
7. **Matching cues — this post is the wrong reference when the user
   mentions**: wanting in-tmux pane splits, in-tmux copy-mode-driven
   clipboard via OSC 52 with mouse selection inside tmux, daemonized
   "agent" workflows (process keeps running *and* takes new actions
   while operator is offline).
8. **For the session picker code**: the picker is zsh-specific (uses
   `${arr[(re)pat]}`, `${(@f)...}`, `print -P`, parameter glob
   numeric tests). It is not portable to bash without a rewrite.
   Don't copy-paste it into `.bashrc`; suggest a bash equivalent
   using `mapfile` / `select` instead, or recommend the user adopt
   zsh for the login shell.

## Closing

If you SSH from a modern terminal and want session persistence without
giving up the terminal's native UX, the path is:

1. Install tmux 3.2 or newer (3.6+ recommended for current
   `terminal-features` semantics).
2. Drop the config in [The full config](#the-full-config) into
   `~/.tmux.conf`. Replace `xterm-ghostty` with your terminal's `$TERM`
   value if different (`echo $TERM` on the remote side).
3. Run multiple shells via your **terminal's** tabs and panes, each
   SSHing into its own named tmux session. Keep tmux single-pane per
   window. (Ghostty's pane model fits this naturally — panes are
   ephemeral scope, tmux sessions are persistent state.)
4. Add the SSH-login picker to `~/.zshrc` for frictionless attach with
   `[running]` / `[attached]` indicators.
5. Add the starship chip (or a TUI statusline indicator) so you always
   know which state you're in.

Three rounds of design review on this exact stack uncovered roughly a
dozen wrong assumptions before landing on this combination, including
two architectural pivots (away from `abduco` and `dtach`) that came from
verifying upstream source against marketing claims. The reasoning is
worth keeping around even if you adopt a different specific config —
the architectural argument (virtual terminal emulator as relay, not byte
pipe) is the durable insight; the config is just one expression of it.

---

*Trade-off honesty*: I do still hit rare "buffer weirdness" with the
`smcup@:rmcup@` override. Almost always when an alt-screen program
crashes or is killed instead of exiting cleanly, leaving its rendered
state stuck in scrollback. The fix in those cases is `clear` or
`reset`, which forces the terminal to redraw. Rare enough that I
prefer this config to the alternative; your mileage may vary.

*Acknowledgements*: this design came out of a multi-round review
process using a second LLM as a code reviewer (the architectural
argument for tmux over `dtach` was specifically a counterargument my
reviewer raised that flipped my initial choice). The reviewer's value
isn't generating the answer; it's catching the wrong answer before it
ships.
