Changelog
Every user-visible change, release by release. The format follows Keep a Changelog and versions follow Semantic Versioning; what counts as a breaking change is in Versioning. Release files are on GitHub Releases.
Unreleased
0.1.5 - 2026-10-21 Latest
Fixed
C-c,C-\andC-ztyped at a job in a shell with job control (zsh,bash -i) run throughzeropty-ttywent to the shell, not the job:sleep 30could not be stopped. A job leads its own process group, and the shell'stcsetpgrpwas swallowed by the shim. The shim now reports it, and zeropty signals that group as a tty does its foreground group, soC-zandfgwork too, pipelines included. Only a process group with a member under the session's program is taken. A shell started before the upgrade keeps the old shim: start it again.
0.1.4 - 2026-10-21
Fixed
zeropty-tty: a passphrase typed atssh,ssh-keygen,ssh-addor any program usingreadpassphraseorgetpasswas echoed onto the session's screen, and so stayed incapture-paneoutput and the scrollback. libc turns echo off there with its own internal calls, which the shim did not reach; the shim now handles these two functions itself on the session's terminal. This covers a program the shim is in, such aszeropty-tty ssh-keygen;/usr/binprograms typed at a shell in the session still run without the shim. Outside a session nothing changes.
0.1.3 - 2026-10-21
Added
- Each release now carries a prebuilt macOS archive,
zeropty-<version>-darwin-universal.tar.gz(Apple silicon and Intel, macOS 11 or later), andSHA256SUMS, so zeropty installs without Rust: download,shasum -a 256 -c SHA256SUMS, unpack, and run from the folder. It holdszeropty,shim.dylib,zeropty-tty,claude-zeroptyand the licenses, signed ad hoc. The embedding libraries and the bash builtin still come from./build.sh(README).
0.1.2 - 2026-10-20
Fixed
zeropty-tty:/dev/ttyopened for reading and writing (fzfwith its stdin piped in) could read keys but every write failed withEBADF, so fzf drew no list and took no pick. A session's terminal is now one socket, as a PTY is one file: stdin, stdout and stderr are that socket (stdin was a pipe), and/dev/ttyin any mode is it.C-dstill ends input for good, and output keeps flowing after it. As on a PTY,O_NONBLOCKset on one of stdin, stdout and stderr is now set on all three. Sessions started by an older server and kept byrestart-serverkeep their input pipe; sessions started by this version should not be handed back to 0.1.1 or earlier (C-dwould not reach them).
0.1.1 - 2026-10-20
Fixed
zeropty-tty: the shim treated any program's fds 0-2 as a terminal, so a program whose stdout was piped or redirected got\e]zeropty-tty;N\amode signals mixed into that output, a program reading/dev/nullas stdin failed (kqueueEINVAL), and children inherited the effect. Now only the session's own pipe and socket count, named by the server in the newZEROPTY_TTY_IDvariable, and mode signals go only to the session's output.zeropty-tty: the signed copy took the name of the link's target (clauderan asclaude.exe), sopsandpgrep claudedid not find it. A program, copied or not, now runs under the name it was run by.display-messagewith-tnaming a session that does not exist now prints the format with the session's variables empty and exits 0, as tmux does, instead of failing with exit 1. To test whether a session exists, usehas-session. Every other command still fails with exit 1, as in tmux.- A missing
-ttarget is now reported in tmux's words. Without a:, a bare name givescan't find pane: x(capture-pane,send-keys,pipe-pane,paste-buffer,resize-pane) orcan't find window: x(resize-window), an id gives the session, window or pane error for$,@or%, andx.0givescan't find window: x;set-environmentgivesno such session:and the target as given. Exit codes are unchanged (1).
0.1.0 - 2026-10-20
The first public version: a session engine for programs that drive many terminal programs (coding agents, scripts, test harnesses), which accepts tmux's command format without a PTY. macOS only. Tagged v0.1.0 on the first public commit, c612089.
Known limitations: macOS only (kqueue, the DYLD shim). One program per session, so there are no windows, panes, status line, copy mode, key bindings or configuration file. wait-pane is not available as an embedded function call, only over the socket (including an embedded listen). Programs protected by SIP or signed with the hardened runtime cannot load the shim, so zeropty-tty runs a re-signed copy instead, and setuid or entitlement programs (top, ps) do not work. End of file with C-d closes the session's input (a pipe has no one-time EOF). Before 1.0, a MINOR release may break the interfaces in docs/VERSIONING.md.
Added
- Server and CLI (
zeropty), with the same arguments as tmux:new-session,has-session,kill-session,list-sessions,kill-server,send-keys(key names compared byte for byte with tmux 3),capture-pane(-p -e -J -N -S -E -b -q),load-buffer,paste-buffer,display-message,pipe-pane,rename-session,resize-window/resize-pane,set-environment -g/show-environment -g,attach-session(-rread-only),-V, and 25 tmux format variables. Unknown flags are errors. The firstnew-sessionstarts the server. - Commands tmux does not have:
wait-pane(waits for text or for quiet without polling),grep-panes(searches the screens of all sessions),pull-sessions/push-sessions(moves running sessions to another process without touching the programs),restart-server(upgrades the server without ending sessions). - No PTY: a session's program talks to pipes, and the engine emulates the line discipline (Enter, the
C-csignal, line editing, echo,C-d). For programs that need a terminal (claude, vim, less, python),shim.dylib,claude-zeroptyandzeropty-ttyanswerisatty,tcgetattr, the window size and/dev/tty. - Screen emulator: 6 bytes per cell, compared with the
vt100crate (0.16) on recorded claude sessions and random input. Quiet screens are folded away and later compressed with zstd. - Socket protocol: one JSON line per request and per reply on
~/.zeropty/mux.sock(ZEROPTY_SOCK), an optional raw reply format, and attach frames. - Embedding: the C ABI (
libzeropty_c.dylib,zeropty.h:zeropty_open,zeropty_command,zeropty_listen, andzeropty_open_polled/zeropty_fd/zeropty_timeout_ms/zeropty_processfor polled use) and the Node module (zeropty.node,client/embedded.mjs) run the engine inside your process.listenserves the same socket protocol from the host process. - Clients:
client/zeropty.mjs(Node, keeps one connection open), and a zsh function (shell/zeropty.zsh) and a bash builtin (shell/zeropty_bash.c) that talk to the socket without starting a process. - Environment variables
ZEROPTY_SOCK,ZEROPTY_HISTORY,ZEROPTY_COALESCE_MS,ZEROPTY_FREEZE_MS,ZEROPTY_COLD,ZEROPTY_COLD_DIR,ZEROPTY_RECORD(docs/USAGE.md). - Benchmarks measured interleaved with tmux 3.7c on one Mac mini M1 (docs/BENCHMARKS.md): 11 of the 14 measured items are at 10x or better, and 3 are not there yet (memory with 400 idle sessions on the standalone server at 6.2x, and
send-keysagainst tmux control mode at 8~10x embedded and 2.2~2.8x over the socket). The reasons are in that document. - A plugin system (Plugin API): plugins receive session create, exit, rename and output events and add new commands. The Rust
Plugintrait (API_VERSION1),plugin-attachover the socket (any language, out of process), C callbacks withzeropty_add_plugin, dylib plugins withzeropty plugin-run/zeropty_load_plugin, an opt-in autostart directoryZEROPTY_PLUGIN_DIR, and a status check withlist-plugins. With no plugins there is no cost, and a plugin that panics, hangs or is slow cannot hold up the engine. - Command calls that do not wait: C
zeropty_command_start,zeropty_call_poll,zeropty_call_due_ms,zeropty_call_free, and NodeEngine.commandStart. zeropty --version: the same output aszeropty -V(zeropty 0.1.0). The zsh function, the bash builtin andclient/zeropty.mjsalso answer it by running the program, as they do for-V.- The format variable
#{version}: the version of the running server (or embedded engine). Adisplay-message -pthat uses only this variable works without a session (zeropty display -p '#{version}'). - C ABI:
ZEROPTY_VERSION_MAJOR,_MINOR,_PATCH,_PRERELEASE,_IS_PRERELEASE,_STRINGand_NUMBERinzeropty.h, andzeropty_version(), which returns the version of the linked library.ZEROPTY_VERSION_NUMBERdoes not include the prerelease, so0.2.0-rc.1and0.2.0have the same value; tell them apart withZEROPTY_VERSION_IS_PRERELEASE. - Node:
version()inzeropty.nodeandclient/embedded.mjs, andVERSIONinclient/zeropty.mjsandclient/embedded.mjs. - The versioning policy
docs/VERSIONING.md: the compatibility surface whose changes count as breaking (the CLI's arguments, output and exit codes, the socket protocol, the session record, the C ABI, the Node API, environment variables,dist/names) and the MSRV (Rust 1.88). Before 1.0, a MINOR release may break this surface, and such changes are announced as**BREAKING**entries. zeropty --help(-h,help) prints the list of commands and options, andzeropty help <command>prints that command's usage and a link to its documentation. It answers without a server.- Documentation: a reworked Korean README, a documentation index (
docs/README.md), a FAQ, runnable examples (examples/), and rustdoc with examples for every public Rust item.
Fixed
- The CLI no longer panics when the reader closes the pipe early (
zeropty capture-pane -p ... | head -1).