Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run tma over ssh

Two arrangements get called “tma over ssh” and they are not the same problem. Work out which one you have first, because only the second needs anything from this page.

tmux on the far side

You ssh to a host, start tmux there, and run agents in its panes. tma belongs on that host too, beside the agents: install it there and everything works exactly as it does locally, because nothing is remote from tmux’s point of view. Your terminal is a client and tma never sees it.

What a fresh connection does want is a way in that lands on the right pane. tma jump is switch-client, so it moves a client that is already attached and does nothing from a terminal that has none. tma attach is the other half: tma attach --pane %5 selects that pane’s window and pane, then replaces itself with tmux attach-session, so one command over ssh (or from a phone’s terminal app) puts you in front of the agent instead of wherever the session was last left. Inside tmux it is jump --pane, so the same command works from both ends.

The only thing your connection changes is where a notification lands. osascript and notify-send fire on the host running tmux, which is not the machine you are sitting at, and neither fails loudly. Notifying from a remote host covers the three sinks that do cross a connection.

ssh from a local pane

tmux runs on your machine, one of its panes runs ssh, and the agent lives on the far side. tma classifies that pane as remote and takes it out of scope, along with mosh, docker, podman, and kubectl. The reason is that neither of the two inspecting tiers survives the hop: the process walk reads your local ps, where the agent does not exist, and a capture would match screen rules against output the agent merely painted through a terminal it does not control. An empty walk plus a plausible-looking screen is how false positives are made.

Concretely, on such a pane:

  • No identity from the process tree, and no screen rules.
  • Hook events are its only possible evidence.
  • Any @agent_* options it still carries from before the hop are held, not refreshed. Nothing is updating them.

tma doctor reports it, and does not call it a problem, because running an agent elsewhere is a choice:

remote:  1 pane(s) behind a remote shell — an agent there reports only if it can reach this tmux socket (see docs/how-to/agents-in-containers.md)
  - %10 work:4.0 (ssh)

See Remote and ignored panes for the rest of that report.

Make a remote agent report

The hook tier inspects nothing, which is why it is the one tier that can cross. tma event takes a pane id, maps one event through a manifest, writes tmux pane options, and exits; it keeps no state between runs and needs no daemon. So an agent on the far side can stamp a pane on your tmux server, provided it can reach the socket and knows the pane id.

That is the same shared-socket arrangement Run an agent in a container spells out step by step. Read it for the details; over ssh only the transport differs. Instead of a bind mount, OpenSSH forwards a unix socket the other way:

ssh -o StreamLocalBindUnlink=yes \
    -R /tmp/tma-tmux.sock:"$(tmux display -p '#{socket_path}')" buildbox

Pick a remote path you can write, and keep StreamLocalBindUnlink=yes: without it a socket left behind by an earlier session makes the forward fail silently while the shell comes up fine.

The rest carries over unchanged. tma and a tmux client whose major version matches your server go on the remote host; the agent’s environment there gets TMA_SOCKET_PATH pointing at the forwarded path and TMUX_PANE set to the local pane id it should stamp; and tma install-hooks <agent> runs on the remote host, where the agent reads its config.

Weigh this before you wire it. A forwarded tmux socket is your tmux server, reachable from the remote host. Anything running there that can open it can drive every pane on your machine, not only the one you meant. tma adds no check of its own: the security model is your user account, and forwarding widens what counts as your user account. Do it for hosts you control.

What you get is the hook tier and nothing else: state, transitions, attention, notifications, tma wait, the picker, tma watch. What you do not get is the fallback, so an event the agent never fires stays unreported, where a local agent would have had its state read off the screen.

There is no cross-host view

tma has no aggregation across machines. One invocation talks to one tmux server, chosen by --socket-name or --socket-path, and there is no command that merges several. Agents on three boxes, each with its own tmux server, means running tma on each box.

What exists instead is enough provenance to merge the output yourself. Every tma ls --json row carries host and server, so (host, server, pane) keys a combined set where a bare %5 would collide; see server and host. Collecting those rows, and deciding what to do with them, is yours to build.

Next