zmx: terminal session persistence in Zig, and what it gives up to tmux
Session attach/detach for the terminal
At a glance
- What is it?
- A session attach and detach tool for the terminal with no windows, no tabs and no splits, distributed as static binaries and packaged into a dozen places.
- Who is it for?
- zmx is a session persistence tool, not a terminal multiplexer, and reading the README with that distinction in mind explains almost all of its design. It keeps a shell alive when the terminal closes, lets several clients attach, and adds send, run, history and tail commands for driving a session without being in it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Zig, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Persistence and reconnection, with no window management at all
The README states the scope in the features list and then states the exclusion in the same list, which is unusual and welcome. zmx persists terminal shell sessions, lets you attach and detach without the session being killed, keeps native terminal scrollback, and allows multiple clients to connect to the same session. Re-attaching restores previous terminal state and output. Then, in bold: this project does not provide windows, tabs or splits.
That single exclusion is the design. A multiplexer earns its complexity by owning your terminal layout, and the cost is a config language, a prefix key that collides with your editor, and a client that has to learn about panes. zmx keeps the process alive and gets out of the way, which is a much smaller promise and one that composes with whatever terminal emulator you already use.
The project describes itself as session attach/detach for the terminal and links to a page titled You might not need tmux, plus a GitHub topic list that includes tmux and ghostty. So the positioning is explicit: it is offered as the thing that solves the one tmux problem most people actually have, which is not losing a long-running process when a terminal window closes.
It works on mac and Linux, according to the README. There is no Windows path in the README and no Windows binary in the download list, which is worth knowing if your fleet is mixed.
Nineteen commands for driving a session, including from outside it
The usage block is a full command reference, and the entries with bracketed letters show that nearly every verb has a short alias. Attaching is `[a]ttach <name> [command...]`, which creates the session if needed. Listing is `[l]ist|ls [--short]` and getting or setting session labels is `[g]et <name>` and `set <name> k=v ...`.
The commands that go beyond a multiplexer are the interesting ones. `[r]un <name> [-d] [command...]` sends a command to a session without attaching to it, which is how you script against zmx. `[s]end <name> <text...>` writes raw input to the session PTY, `[p]rint <name> <text...>` injects text into the session display without becoming input, and `[wr]ite <name> <file_path>` writes stdin to a file through the session. That trio lets a build pipeline push content into a live session and write results back out, which is a scripting surface tmux does not offer in the same form.
Two read commands round it out. `[hi]story <name> [--vt|--html]` outputs session scrollback, and `[t]ail <name>...` follows session output, so a script can consume what a session prints. There is also `[w]ait <name>...` to block until session tasks finish, `print-env [-s] <name> [key]` to inspect tracked environment variables, and `[v]ersion`, which prints version and metadata including the socket directory and log directory.
Detaching is `[d]etach`, which detaches all clients, or `ctrl+\` for the current client only. The README calls closing the terminal window the recommended way to detach, and provides the `ZMX_NO_DETACH_KEY` environment variable if you need that key combination for something else, such as vim's own escape from `:terminal`.
The nix shell example repeats nix run on its second line
The NixOS section offers two blocks and the second one contains a copy and paste error worth naming, because it is the kind of thing that costs a reader an hour. The first block is for running immediately without installation:
nix run github:NixOS/nixpkgs/nixpkgs-unstable#zmx
# or build main yourself
nix run github:neurosnap/zmxThe second block is introduced as a way to start a shell with zmx available while it runs, which is what `nix shell` does:
nix shell github:NixOS/nixpkgs/nixpkgs-unstable#zmx
# or built main yourself
nix run github:neurosnap/zmxThe first line correctly uses `nix shell`. The second line, under a comment that says or build main yourself, still says `nix run`. Inside a `nix shell` block you want the binary on PATH and the command to exit, so `nix shell github:neurosnap/zmx` is what the sentence asks for. Running `nix run` there launches zmx with no arguments, which prints usage and exits.
None of this is a design problem. It is a documentation bug in a project where everything else in the README is unusually careful, and it is a useful signal about the project overall: the prose is precise about behaviour and occasionally careless about exact command text. Check the second line against the docs site before pasting it.
Nested sessions are unsupported, and the failure mode is someone else's terminal
This is the sharpest edge in the tool and the README is admirably direct about it. Inside a session, `ZMX_SESSION` is set, and `attach` reads it: instead of creating a second client, it switches the calling terminal to whatever session you named.
The consequence is spelled out. A script, a build tool or a coding agent started inside a session inherits `ZMX_SESSION`, so running `zmx attach other` from there does not open a new view. It moves the terminal somebody was using, and the session that terminal was showing is left with no client at all. You have not nested a session, you have stolen a terminal.
The fix for interactive use is to unset the variable:
env -u ZMX_SESSION zmx attach otherThe fix for automation is better, because it avoids the problem rather than stepping around it. `zmx run` never switches the caller, so anything scripted should use it. That distinction between run and attach is the one to internalise if you are integrating zmx into a build system or an agent loop.
The same variable solves a separate problem. When you attach, zmx gives you no visual indication that you are inside a session at all, which the README states as a deliberate choice. It does set `ZMX_SESSION` to the session name, and the README provides ready-made prompt snippets for fish, bash, zsh, powerlevel10k, oh-my-posh and Starship. The bash and zsh version is a two-line conditional that prepends the session name in square brackets to `PS1`.
Static binaries for four platforms, then a dozen package managers
The install story is unusually complete. Direct binaries are offered as four tarballs at version 0.8.1 for linux-aarch64, linux-x86_64, macos-aarch64 and macos-x86_64, which covers both Apple Silicon and Intel Macs plus both Linux architectures. Homebrew and mise are both one-liners:
brew install neurosnap/tap/zmxmise use zmxThen the distribution list widens considerably. There are links for Alpine Linux, two Arch packages that track releases and track git separately, openSUSE Tumbleweed, a Gentoo overlay hosted on Codeberg rather than GitHub, and an RPM packaging project maintained by a third party under the engie organisation. That last distinction matters in practice: only the Homebrew tap is first party, and the two Arch packages exist because tracking a moving binary and tracking the branch are different needs.
Building from source needs Zig v0.16, and the command writes into a prefix you have to add to your PATH yourself:
zig build -Doptimize=ReleaseSafe --prefix ~/.localReleaseSafe rather than ReleaseFast is the interesting choice. It keeps runtime safety checks in exchange for a slower hot path, which is the right trade for a program whose job is holding your shell process alive. The tree shows how the release pipeline is put together: a plain `Dockerfile` for development, `Dockerfile.release` and `Dockerfile.upload` for producing and shipping artifacts, `brew.tmpl` and `index.tmpl` as templates with a `gen-brew.sh` script to render them, and `kitty_gfx.sh` for terminal graphics. The development Dockerfile is a debian:12 image that installs curl, git, bats, coreutils and python3, then fetches Zig 0.16.0 from ziglang.org and checks the architecture before downloading.
A release history of three empty bodies and a maintainer who also ships pico.sh
The three most recent releases are v0.8.0 on 2026-09-04, v0.7.1 on 2026-08-27 and v0.8.1 on 2026-09-05, and every one of them has an empty body. That is worth knowing if you are trying to work out what changed between versions, because there is nothing in the release notes to read. The CHANGELOG.md file is in the tree, so the information exists somewhere, just not in the place a package manager would show you.
The dates say the project is moving fast. Three releases in ten days, on top of a repository pushed on 2026-09-25, is a pre-1.0 tool at a high clip, with 31 open issues against 2171 stars and 159 forks. The README's own version references are consistent with this, since the binary download URLs are pinned at 0.8.1, matching the newest tag.
The sponsorship line points at pico.sh, and there is a `pici.sh` script in the tree, which indicates the author builds infrastructure products rather than this being a one-off utility. That context explains a few choices. The scoped design is deliberate rather than incomplete, since the author has other projects that need this behaviour and not tmux's layout management. The breadth of unofficial packaging is less deliberate and is the kind of thing a maintainer with limited time simply accepts.
The license is MIT per the repository metadata, with a LICENSE file in the tree. The repository is not archived.
Editorial conclusion
zmx is a session persistence tool, not a terminal multiplexer, and reading the README with that distinction in mind explains almost all of its design. It keeps a shell alive when the terminal closes, lets several clients attach, and adds send, run, history and tail commands for driving a session without being in it. It gives up windows, tabs and splits, which is exactly the complexity tmux carries. The pieces to understand before adopting are the nested session behavior driven by `ZMX_SESSION`, which will move a terminal out from under a running build tool, and the requirement to add your own prompt indicator, since zmx deliberately gives you no visual cue that you are inside a session. Install the binary, set the prompt variable, and unset it in anything that attaches on its own behalf.
Frequently asked questions
How do I detach from a zmx session?
Close the terminal window, which is what the README recommends, or press ctrl+\ to detach the current client only. The zmx detach command detaches all clients at once. If you need that key combination for something else, set ZMX_NO_DETACH_KEY to disable the shortcut and detach by closing the window or running the command.
Does zmx support nested sessions?
No, and the README explains the failure mode. Inside a session ZMX_SESSION is set and attach reads it, so running zmx attach other from a build tool or agent started inside a session switches that terminal to the other session and leaves the original with no client. Use zmx run for non-interactive work, or unset the variable with env -u ZMX_SESSION.
How do I install zmx on macOS or Linux?
Prebuilt tarballs are published for linux-aarch64, linux-x86_64, macos-aarch64 and macos-x86_64 at version 0.8.1, and there is a Homebrew tap with brew install neurosnap/tap/zmx. mise use zmx also works. Building from source needs Zig v0.16 and the command zig build -Doptimize=ReleaseSafe --prefix ~/.local.
How do I tell which zmx session I am attached to?
zmx gives no visual indication that you are inside a session, but it sets ZMX_SESSION to the session name. The README supplies prompt snippets for fish, bash, zsh, powerlevel10k, oh-my-posh and Starship that prepend the name to your prompt when that variable is set. A plain shell conditional on the variable is enough for most people.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/neurosnap-zmx)