Remote tmux Mirroring
Remote tmux connects cmux to a server over SSH, attaches to its tmux sessions in control mode (tmux -CC), and shows them with the cmux interface. Sessions, windows, and panes appear as workspaces, tabs, and splits with native scrollback and copy, while every change is applied to the real tmux server on the other end. The feature sits behind a Beta Features toggle and leaves local terminals alone.
Before you start
The remote machine needs tmux 3.2 or later and must be reachable over SSH; host aliases from ~/.ssh/config work. Stock tmux already includes control mode. Before attaching, cmux asks the server for its tmux version and stops with an explicit error if it is older than 3.2. The connection uses an SSH ControlMaster, and the tmux server stays up after you detach.
Turning it on
Remote tmux is a beta feature and starts turned off; until you enable it under Settings → Beta Features, local terminals are unaffected.
Connecting to a host
Run cmux ssh-tmux <destination> in a terminal — an ~/.ssh/config alias or user@host. cmux mirrors that host's tmux sessions into the current window's sidebar: each session becomes a workspace, each window a tab, and multi-pane windows become in-tab splits.
# user@host, or any Host alias from ~/.ssh/config
cmux ssh-tmux build@devbox.internal
# non-default port and key
cmux ssh-tmux staging --port 2200 --identity ~/.ssh/staging_ed25519
# put the mirror in its own cmux window
cmux ssh-tmux build@devbox.internal --new-windowHosts that authenticate non-interactively (ssh-agent, or a key in ~/.ssh/config) attach with no prompt. If the host needs interactive authentication (a password, host-key confirmation, or MFA), cmux runs ssh inline in your terminal so you can authenticate, then mirrors into the current window. Accepts --port, --identity, and --no-focus.
Add --new-window to put the mirrored sessions in a separate cmux window rather than the one you ran the command from.
Fixing "Permission denied"
cmux passes the destination to ssh unchanged. A Host alias therefore has to supply every login detail on its own, usually User (otherwise ssh logs in as your local account name) and IdentityFile. For example:
Host staging
HostName 198.51.100.24
Port 2200
User deploy
IdentityFile ~/.ssh/staging_ed25519You can also name the user in the destination, as in cmux ssh-tmux dev@my-ssh-alias. As a quick test, run plain ssh with the same destination: if that cannot log in, cmux ssh-tmux fails the same way.
tmux objects and their cmux equivalents
The two tools nest things in opposite orders. A tmux window is divided into panes, while a cmux pane carries a strip of tabs with one terminal per tab. cmux reconciles them by translating each tmux level into a cmux container:
| In tmux | Appears in cmux as |
|---|---|
session | Its own workspace in the sidebar |
window | One tab inside that workspace |
pane | One side of a native split within that tab |
As a result, one cmux tab can hold several terminals: a tmux window with multiple panes is laid out as a real cmux split inside its tab, and every remote pane gets the usual cmux terminal chrome. Changes also flow back. Splitting or closing one of those panes is applied to the tmux window with split-window, and reordering the tabs reorders the tmux windows with swap-window.
Supported behavior
- Splits: every pane you add or remove in a mirrored tab becomes a tmux split-window call, and cmux redraws from the layout tmux reports back. Scripted splits (cmux new-split, surface.split) return accepted without a surface id, and the pane shows up after tmux applies the change. tmux cannot take a startup command, working directory, divider position, or left/up placement through this path, so cmux rejects such requests immediately and leaves the remote session untouched.
- Grid size: cmux resizes the remote client to the grid it actually draws (refresh-client -C), so full-screen programs get real dimensions instead of the tmux default of 80×24.
- Mouse input: when the remote program turns on mouse mode it receives clicks, wheel scrolling, and drags. To select and copy text with cmux instead, hold Shift while you drag, as you would with any local mouse-aware program.
- Paste and drag-and-drop: text you paste and images or files you drop are delivered with tmux paste-buffer -p, so the remote program (a coding agent, for example) sees a genuine bracketed paste. An image shows up there as [Image #N], not as a path on your Mac.
- Clean Unicode: when tmux breaks a multi-byte character across two output chunks, cmux joins the pieces again, so box drawing and wide glyphs display correctly.
- Current folder: cmux follows the working directory of each remote pane and displays it on the tab.
- Tab order: dragging mirrored tabs into a new order rearranges the tmux windows with swap-window.
Under the hood
The mirror is a plain ssh … tmux -CC attach process. cmux implements the control-mode client itself: it matches each reply block (%begin … %end) to the command that caused it, streams every %output notification into the terminal surface for that pane, and sends your keys and mouse events back as tmux send-keys. Layout is authoritative on the server, so cmux sizes its surfaces from what tmux reports and does no local reflow.
Socket API
The feature is also driven by socket commands (gated on the Beta Features flag). host is an SSH destination or ~/.ssh/config alias; session is a tmux session name; mirror also accepts standard window/workspace/surface/pane routing.
The remote.tmux.* socket commands (below) give finer control. remote.tmux.mirror is the mirror entry point; it accepts window/caller routing and can optionally select the mirrored workspace.
| Method name | Parameters | What it does |
|---|---|---|
remote.tmux.mirror | host, port?, identity_file?, activate? | Mirror every session on a host into the resolved window, each as its own workspace (windows become tabs); returns the ssh command to run for interactive auth when the host needs it. |
remote.tmux.sessions | host, port?, identity_file? | Returns the tmux sessions that exist on the host. |
remote.tmux.attach | host, session, create? | Starts a control client on one session. With create, a missing session is made first. |
remote.tmux.state | host, session | Diagnostic snapshot of what the control client currently sees. |
remote.tmux.detach | host, session | Stops the control client and leaves the remote session alive. |
{"method": "remote.tmux.sessions", "params": {"host": "staging"}}To block SSH option injection, a host or identity file that begins with a dash is refused as soon as the request arrives.
Known limitations
- Losing SSH briefly does not end the mirror. cmux freezes the panes, reconnects with a capped exponential backoff, and repopulates them once the link is back. Three things end a mirror permanently: the remote session exiting, closing the mirror window, or quitting cmux. After a relaunch, start it again with cmux ssh-tmux; mirrors are not part of session restore.
- Main-screen scrollback is not rewrapped when the size changes, because reflow is left to tmux; older lines can keep their previous width until the program redraws. Full-screen programs repaint on resize and are unaffected.
- Bracketed paste through paste-buffer -p covers single-line content only, such as a dropped file or image path. Text spanning several lines is typed as ordinary keystrokes, so the remote program does not treat it as one paste.
- Tracking the working directory relies on control-mode subscriptions, which is the reason cmux will not open a mirror against tmux older than 3.2.