Hear me out, I can already read the descenting opinions:
- But I need session persistence!
- But I need split windows!
- But I need to group windows per project!
- But I need lots of terminals inside of a remote server!
I had the exact same response whenever someone would argue against using tmux
.
For context, I've been a huge fan of tmux
and have been using it as a daily
part of my workflow for 7+ years. Whether I'm developing on my local machine or
in SSH, I was using tmux
.
However, a couple of years ago I stumbled across a GitHub issue in the kitty project that has stuck in my mind like an itch that I cannot scratch.
In summary: multiplexers add unnecessary overhead, suffer from a complexity cascade, because they actually have to translate escape codes, modifying them in hackish ways to get them to work with their concepts of windows/sessions.
And then a couple of weeks ago I watched this excellent interview from
linkarzu
and kovid (creator of kitty). In it, they
discussed tmux
.
I have to say, the arguments are convincing. Over the years, I have had a
handful of problems with tmux
that were not necessarily deal breakers but very
annoying to deal with on a regular basis.
For example, if you do not set TERM
with tmux
properly, your colors will
render incorrectly. Without tmux
everything looks good, but with it things can
look washed out or just wrong. So when debugging issues related to terminal
features, you need to consider both your terminal emulator and tmux
. Now add
another layer like ssh
and you're really pulling your hair out.
Another example is buffer scrollback. It's one of those things where you have to
learn the tmux
way of scrolling a window. You get used to it, of course, but
it's just not great.
And what about mouse select to copy/paste? It works most of the time, but
sometimes tmux
gets ignored and I'm selecting across splits which makes the
thing I'm copying impossible to grab without bailing.
Another issue I ran into a couple of times is the lack of experimental protocol
support. I use "experimental" loosely since some of them are gaining popularity
inside terminal emulators, like the
kitty graphics protocol.
Even if your terminal emulator supports the kitty graphics protocol,
tmux
does not.
This is why kovid argues that terminal multiplexers:
"[...] act as a drag on the ecosystem as a whole, making it very hard to get any new features."
Okay, I get it, I'm sympathetic to the issue terminal multiplexers cause to the
wider terminal ecosystem, but what's the alternative? tmux
solves problems
that are not easy to replace:
- session persistence (detach + attach process from terminal)
- window management (tabs, splits, moving them around)
Session persistence can be accomplished in a number of ways, with varying
degrees of feature overlap with what tmux
provides:
ctrl-z
+fg
nohup {cmd} &
disown
These are discussed in this
stack exchange
post. None of them quite work the way tmux
works. In particular, these
commands will either a) get killed when you close your terminal, or b) cannot
reattach easily.
For window management, the argument is we should use our window managers to manipulate our windows. That works great for local development, but what happens when you are SSH'd into a server? How do you let your window manager manipulate windows within an SSH session?
Darn, built-in tools don't quite check all of our boxes, I wonder, are there any
tmux
alternatives that could work?
I was desperate for a solution and I really did not want to build my own. I also
didn't want to replace tmux
with something equally as large of a dependency.
Here's what I found:
All of these tools embody the unix philosophy of doing one thing, well. So they are targeting session persistence (detach and attach functionality). What's exciting about this idea is since there are no virtual splits, I can get native scrollback!
Most of them work by creating a daemon1 with some permutation of fork()
and communicating with a unix socket between the detached process and "client"
terminals. Some of them support buffer playback so when you reattach you can see
where you left off. Most of them haven't quite nailed this aspect of what tmux
does well, but shpool
does a pretty good job.
I tried all of them with varying degrees of success. They could do what they
claim, but they are buggy. In particular, I could not get them to detach when
inside of nvim
, which is a deal breaker. I assumed it was because the
shortcuts I was using to detach were being captured by nvim
. Who knows, it
might be something misconfigured on my end.
However, out of those tools I used, shpool
checked the most boxes.
Specifically they have a command shpool detach
which allowed me to run a
command to detach the current process. So I was able to create a keymap that
sent the detach command to shpool
:
1-- https://github.com/shell-pool/shpool/issues/71#issuecomment-2632396805
2vim.keymap.set({ "n", "v", "i", "t" }, "<C-space><C-d>", function()
3 vim.cmd("!shpool detach")
4end)
Nice, this is working well for session persistence. Let's move onto window
management. I'm using ghostty
on my work laptop and sway+foot
on my personal
machines. I can use both of them for window management, but as a note I employ a
client+server development workflow. This means my client (e.g. laptop, desktop)
connects to a headless VM on my proxmox
box where I do all of my dev. So I am
always SSH'd during development. So how can I use my local window managers to
control all my shpool
sessions on a remote server?
shpool
has some docs on how to
automatically connect to shpool
The idea is simple: use your window manager to create new terminal windows and
then ssh
into each shpool
session. Here's the ssh_config
I use to make
this easier:
Host *
# ping server to make sure it's still responding
ServerAliveInterval 60
ServerAliveCountMax 3
Host = d.*
HostName 192.168.88.xxx
User erock
IdentityFile ~/.ssh/id_ed25519
# attach to shpool session based on the name provided
RemoteCommand shpool attach -f %k
RequestTTY yes
# share ssh connection with all terminals that try to connect to this host
ControlPath ~/.ssh/cm-%r@%h:%p
ControlMaster auto
ControlPersist 10m
Now I can automatically create or attach to my shpool
sessions:
ssh d.chat
-> connect to IRCssh d.dot
-> connect to my dotfilesneovim
clientssh d.term
-> connect to a running terminalssh d.pico
-> connect to my piconeovim
client
I can spin up as many shpool
sessions as I want and connect to them as-needed.
Then when I combine this with autossh
,
I can keep my terminal windows open on my client and reconnect automatically
when the client reconnects to my network!
1autossh -M 0 d.chat
So, did I finally replace tmux
? For me, the answer is a resounding yes! Once I
got all of this setup on my dev machine, I haven't used tmux
or feel like a
massive downgrade. I did have to adjust my normal workflow slightly, but that's
been fun. Further, I'm slowly noticing things that tmux
didn't handle well,
but now, "just work": native scrollback, terminal notifications, and terminal
titles being the most notable changes.
It's not perfect, there are active issues with shpool
, like it doesn't restore
terminal state properly when reattaching which can make
resizing broken while using nvim
.
But as noted in that issue, there is a work-around:
1vim.keymap.set("n", "<leader>l", function()
2 io.stdout:write("\027[?2048h")
3end, opts)
shpool
also
doesn't support "multiplayer"
which has caused some issues with using autossh
on multiple clients since they
will disconnect each other.
Could this workflow replace tmux
for you? Let me know!