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. But tmux has mouse scrolling support so what's the issue? It's not using your native terminal emulator's scrollback, it's using its own buffer and scrolling mechanism. It works most of the time, but sometimes my terminal emulator will ignore tmux split boundaries and now 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.
I watched another video recently where Mitchell Hashimoto discusses the same issue with terminal multiplexers:
[...] A terminal multiplexer is itself a full fledged terminal where its UI is just text that's going to another graphical terminal. Like you're running multiple levels of terminals and one of the first places that causes issues is there's features that
ghosttysupports that because something liketmuxdoesn't support you no longer get [to use those features].
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)
- terminal state and scrollback restoration
- window management (windows, 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+fgnohup {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?
Wezterm does natively support tmux's control mode which allows you to create new windows in the emulator which then creates tmux windows automatically. So you can connect to a tmux session and wezterm will automatically open windows and splits. Ghostty is also working on similar support.
Having said that, I don't want to be locked into using a particular terminal emulator. In fact, I use a mix of them depending on the machine. I want my dev workflow to be independent of the terminal emulator I have running.
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 (spoiler: I did). 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 daemon[^1] 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. This is primarily because these tools do not fully support the kitty keyboard protocol.
Further, only shpool supports the ability to fully restore your terminal session when re-attaching to a session. However, they are using a vt library for rehydration that doesn't perfectly restore your terminal state which can cause your session to only have some of your terminal features enabled on re-attach. This is a long standing issue being discussed. Basically, the core issue is that when you open a new session in your terminal, your shell and programs will query your emulator to determine what features it supports. This will enable or disable certain features in your terminal, shell, and programs. So when restoring a terminal session on re-attach, the tool must properly set the correct modes, features, etc., or else things might not render properly on the screen.
shpool also doesn't support "multiplayer" which has caused some issues with using autossh on multiple clients since they will disconnect each other. This is a huge problem if you have multiple machines that you use to connect to the same sessions. For example, I have a desktop that is always online and a laptop that is sometimes online. They are constantly disconnecting each other from my terminal sessions.
Fast forward a couple of months there were enough issues with it that I decided to try and build my own session persistence tool. I knew that libghostty was coming and that I could potentially integrate with it. So after reading that post I decided it was time to create my first project in Zig.
This led me to create zmx which is a fusion of all the good ideas from the other tools combined into one. It uses abduco's one daemon per session architecture, shpool's state restoration idea but using libghostty, and tmux's multiplayer support.
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.
Could this workflow replace tmux for you? Let me know!
[^1]: 1.7 How do I get my program to act like a daemon?