main
250 commits (250 out of 446 generated)
cd88d1b
NgoQuocViet2001  ·  2026-08-14
main
fix: allow global session wildcards
bea5fe3
David van Munster  ·  2026-08-11
build(nix): regenerate lockfile (#236)
4cab009
David van Munster  ·  2026-08-11
build(nix): bump zig2nix to 0.16.0 (#235)
92f2425
Eric Bower  ·  2026-08-11
fix(pty): preserve terminal width and prevent readline truncation in run tasks

- Fetch terminal size in daemonize() before stdio redirection to /dev/null
- Send .Resize before .Run in zmx run to match client terminal size
- Default TERM to xterm-256color when unset/dumb to prevent readline horizontal scroll artifacts
347fbf4
Eric Bower  ·  2026-08-11
docs: changelog
7a001c4
Eric Bower  ·  2026-08-11
fix(daemon): sync pwd on IPC queries and add wait_for_cwd test helper

Ensure daemon.cwd is updated from the terminal emulator state when handling
IPC Info and History requests. This prevents zmx list from reporting stale
working directory state if queried before the PTY read loop calls setPwd.

Also add a deterministic wait_for_cwd helper to test_helper.bash so BATS
OSC 7 tests poll zmx list until the expected CWD is populated, fixing host-side
test race conditions.
e4e7ad7
Chance Zibolski  ·  2026-08-05
fix(cwd): decode the OSC 7 cwd before the chdir

`Daemon.cwd` holds the OSC 7 value, which is percent-encoded, and nothing
decoded it before opening the directory. The chdir on session create
failed on any escaped character:

    [warning] failed to open dir=/tmp/zmx%20spaced%20dir err=FileNotFound

The new session then silently landed in the daemon's inherited cwd.

`util.parseOsc7Cwd` decodes the value to an openable path and reports
whether its host is this machine. `Daemon.setCwd` keeps both: `cwd` stays
in `file://<host><path>` form so `zmx list` still shows the host, which is
what tells you a session is inside SSH, and `cwd_path` holds the decoded
path used for the chdir. A caller that supplies a plain path (`zmx run`,
`zmx attach`) gets a URI built by `util.toOsc7Cwd`, so the stored value
has one shape regardless of source.

`cwd_path` is left empty when the cwd is on another host, since OSC 7
crosses SSH boundaries and the directory does not exist here. That check
previously used `mem.eql` against gethostname(), so a missing host,
`localhost`, and short-name/FQDN mismatches all read as remote and
disabled the chdir even locally.

Refs #222
a0fc894
Chance Zibolski  ·  2026-08-04
fix(attach): drop stray NUL from replayed OSC 7

The formatter's `extra.pwd` writes `terminal.pwd.items`, and
`Terminal.setPwd` appends a NUL sentinel to that buffer, so every OSC 7
we replayed carried a stray \x00 before the terminator:

    \x1b]7;file://HOST/private/tmp\x00\x1b\\

Terminals that only apply the value tolerate it. One that records what
it receives persists the NUL and then fails to parse its own state back,
which is what left kitty unable to start.

Emit OSC 7 from `getPwd()` instead, which returns the same bytes without
the sentinel. `serializeTerminal` had the same bug, so `zmx history --vt`
was emitting it too.

Refs #222
00897d8
Eric Bower  ·  2026-08-11
refactor: ZMX_TASK_COMPLETE now uses a 4ch hex id for task detection

This ensures we are correctly scanning for the task that was actually triggered
from the current daemon.  A zmx session is allowed to `zmx run` to another session
and then it immediately tails that command.  That command will also output
the ZMX_TASK_COMPLETE marker which could mark the parent session as complete
erroneously.

We now look for `ZMX_TASK_COMPLETE:wxyz:0`
b7033a8
Chance Zibolski  ·  2026-08-05
build: fetch the zig tarball for the host arch

The Dockerfile hardcoded the x86_64 tarball, so on an arm64 host the
image ran under emulation and `zig build` died before compiling anything:

    error: rosetta error: bss_size overflow

That made `zmx run build`, `test`, `fmt`, and `integration` from pico.sh
unusable on Apple silicon.

`uname -m` already reports the names zig uses (x86_64, aarch64), so
substituting it needs no mapping table. Unknown arches now fail with
their own name rather than a 404 from ziglang.org.

Also drop `-v` from tar, which printed a line per extracted file, and
remove the tarball after extracting it.
bdea415
Eric Bower  ·  2026-08-03
docs: changelog
4f3739e
Varun Biniwale  ·  2026-07-08
feat(attach): add ZMX_NO_DETACH_KEY to free up ctrl+\ for inner programs

ctrl+\ as the detach key is hardcoded with no way to opt out, which
conflicts with programs that use it themselves (e.g. vim's ctrl+\
ctrl+n to escape its own :terminal). Since the byte is intercepted in
the client's input loop before it ever reaches the remote PTY, no
remapping on the inner program's side can work around it.

Setting ZMX_NO_DETACH_KEY (to any value) skips the ctrl+\ intercept so
it passes through as normal input; zmx detach or closing the window
still work as usual.
70476a7
Eric Bower  ·  2026-08-03
chore: remove pty data from logs

Closes: https://github.com/neurosnap/zmx/pull/227
85b045c
Eric Bower  ·  2026-08-01
feat: track cwd

We now update the session's cwd whenever it changes based on OSC7
ansi escape codes.

Now when switching to session B from within session A, we now correctly
set the dir to where session A left off.

Because we are tracking cwd based on OSC7, embedded within cwd is the
hostname (`std.Uri`) so we know not to try to chdir when the session
is ssh'd into a remote machine.

BREAKING CHANGE: static `start_cwd` has been replaced by dynamic `cwd`
3697982
Eric Bower  ·  2026-08-02
fix(log): race between checking the length of the file and writing to it
0843272
Chance Zibolski  ·  2026-08-01
fix: replay the window title on attach (#224)

The formatter has no title extra and never emits OSC 0/1/2, so a client that
attaches to an existing session shows whatever its terminal falls back to,
usually the name of the client process, even though the title is tracked in
the terminal state.

Programs that set a title only emit it on their own schedule, so a shell
reprints it at the next prompt but a long running program does not reprint it
at all, leaving the wrong title for as long as it runs.
8a6d038
Eric Bower  ·  2026-08-01
fix: max_scrollback buffer api for libghostty changed
027afd7
Eric Bower  ·  2026-08-01
chore: update libghostty
85697d5
Chance Zibolski  ·  2026-08-01
fix: build on macOS with zig 0.16.0 (#223)

* fix: cast log_mode to mode_t for File.Permissions.fromMode

cfg.log_mode is u32 while fromMode takes mode_t, which is u16 on macOS, so
these two call sites fail to compile there. cfg.zig already casts for the
equivalent Dir.Permissions calls.

* fix: use the platform signal type in the wake handler

The handler was declared with std.os.linux.SIG, so sigaction rejects it on
platforms where that is not the native type and the build fails on macOS.
posix.zig already re-exports SIG for this purpose.
266a733
Eric Bower  ·  2026-07-31
fix(switch): reset screen and pass shell to next daemon
fa93c35
Eric Bower  ·  2026-07-30
docs(daemon): copy tweaks to comment
a39b3d0
Eric Bower  ·  2026-07-30
fix: revert umask and chdir
0b44313
Eric Bower  ·  2026-07-30
docs: daemonize
6171338
Eric Bower  ·  2026-07-30
chore: ensure robust daemon

This is mostly for posterity: we setup the daemon process to chdir to "/" and
set the umask to 0. This ensures the daemon is not on a dir that could be
unmounted and then the unmount would fail (like a usb drive) and we reset the
umask so the daemon doesn't unherit an unpredictable umask.
0d66096
Eric Bower  ·  2026-07-27
refactor(daemon): restructure io and allocators

Because we perform a double-fork when spawning a daemon we have to be careful
about multithreading and mutex locks within io and allocators or else we could
end up with a deadlock.  I don't fully understand how this happens within the
DebugAllocator but I've hit deadlocks previously.  So I restructured the Daemon
to not store io or an allocator and made it much more clear what is going on
and why we have to create a new allocator after the double-fork.

I also extracted the daemonize logic into its own file and set of fns mainly
because that code doesn't need to change much and it is mostly fork() machinery.
9e5eb55
Eric Bower  ·  2026-07-26
refactor: use posix.zig for all posix fns

The zig team is planning to completely kill std.posix so we might as well
get ahead of it and bring everything we need into zmx.
4232d74
Eric Bower  ·  2026-07-23
refactor: upgrade to zig v0.16.0

The biggest challenge was the deprecation of std.posix.  In an effort to upgrade
to 0.16 without a major rewrite I decided to port the functions from std.posix
that we were heavily using.  We can continue to chip away at some of the posix
functions but it's likely many will remain until std.Io matures and we can migrate
off of using `poll(2)` directly.

Build is passing, tests are passing.
93cd148
Eric Bower  ·  2026-07-23
fix: improve ci script build times
06c472d
Eric Bower  ·  2026-07-23
fix: ci release script
d221e86
Eric Bower  ·  2026-07-23
chore: update zmx to v0.7.0
9bcc7bc
Eric Bower  ·  2026-07-23
docs: prep v0.7.0
8ba312d
凡辞  ·  2026-07-22
fix(send): preserve client leadership
935c484
Siva Mahadevan  ·  2026-07-23
build: canonicalize ghostty dep URL (#206)

While here, only import build.zig.zon once in build.zig.
3817645
Martin Häcker  ·  2026-07-22
Add nix installation instructions (#203)
fa80e7a
Eric Bower  ·  2026-07-22
docs: changelog
94d0675
Simon Labrecque  ·  2026-04-27
fix(attach): reclaim leader on kitty keyboard input

When a non-leader client sends keyboard input like:

- `\x1b[102;1:1u`
- `\x1b[57444;1:1u`

libghostty warns and does not classify the sequence as user input. As a result,
handleInput() treats the payload as non-user input and refuses to transfer
leadership to the active client.

In practice, that leaves a re-attached client unable to take control even
though its input is reaching the daemon.
d3b01c7
MyeongjunKim  ·  2026-04-08
fix(attach): detect ctrl+\ encoded by xterm modifyOtherKeys

PR #92 made the ctrl+\ matcher handle the kitty CSI u encoding. The
older xterm modifyOtherKeys encoding (CSI 27 ; mod ; key ~) was still
missed, which broke detach when the inner program (e.g. claude) enables
modifyOtherKeys level 2 and the outer terminal honors it (e.g. iTerm2).

Add a separate matcher mirroring keypressWithMod, with the same lock
modifier tolerance. Also force modifyOtherKeys back to level 0 in the
detach restore sequence; xterm modifyOtherKeys has no push/pop, so this
is unconditional, mirroring the existing unconditional disables for
mouse modes, bracketed paste, focus events, and alt screen. Without
it the outer shell ends up encoding ctrl combos as escape sequences
after detach, corrupting input at the prompt.
5778a84
Eric Bower  ·  2026-07-22
docs: readme
61e5cb2
Zixuan Song  ·  2026-07-22
doc: add alternative gentle-hint pattern for server SSH workflow (#175)

The existing session-picker docs only show an auto-launch pattern
(zmx-select && exit), which forces the picker on every SSH login.
This is inconvenient on shared servers where quick commands are common.

Add an alternative 'gentle hint' pattern that shows a one-line
reminder when sessions exist, without blocking the shell.
4ab0a60
Eric Bower  ·  2026-07-22
fix: use BRANCH
93247e9
Chris Rose  ·  2026-07-04
fix: avoid statx syscall so zmx runs on pre-4.11 kernels (#186)

zmx crashed with `error: Unexpected` on every invocation on a Synology
DiskStation (Linux 4.4.302). `error.Unexpected` is what Zig's std returns
for an unrecognized errno; here the errno is ENOSYS (38) from the `statx`
syscall, which only exists on Linux >= 4.11.

Two code paths issued statx via Zig std, which calls the raw statx syscall
directly and does not fall back like libc does:

  - log.zig: LogSystem.init() called file.getEndPos() -> File.stat() -> statx.
    This runs before command parsing, so it broke *every* subcommand, not
    just attach.
  - socket.zig: sessionExists() called Dir.statFile() -> statx.

Both now use fd/at-based stat instead:

  - LogSystem.init() uses posix.fstat(file.handle).
  - sessionExists() uses posix.fstatatZ() + S.ISSOCK().

Because zmx links musl, these resolve to SYS_fstat / SYS_fstatat (musl on
x86_64 skips statx entirely), which are available on 4.4. Verified by
disassembling the release binary: no reachable statx call remains on the
attach path (the only residual references are the DWARF stack-trace
unwinder and sendFile fast-paths, neither of which zmx exercises).
444fa72
Chris Rose  ·  2026-07-22
docs: add mise install instructions (#198)
e404ed6
Aaron Bull Schaefer  ·  2026-07-22
docs: add zmx-picker to community tools (#199)
22997b7
Eric Bower  ·  2026-07-20
fix: ghostty resize fn changes type sig
62e7e56
Eric Bower  ·  2026-07-20
docs: readme
7781824
Eric Bower  ·  2026-07-20
fix: HyperlinksetOutOfMemory issue

This updates libghostty which fixes a SIGABRT from hyperlink rendering.

Closes: https://github.com/neurosnap/zmx/issues/182
Reference: https://github.com/ghostty-org/ghostty/pull/13394
8d5e827
Max Rydahl Andersen  ·  2026-06-29
feat: session labels

Now you can apply ephemeral key-value pairs to a zmx session and see them
when running `zmx list`.  This allows users to tag sessions with metadata
instead of only relying on the session name.
ce52eb7
Eric Bower  ·  2026-07-17
fix: flaky bats tests
fbf900c
Noelle Leigh  ·  2026-07-04
fix: Add missing commands to completion scripts

- Add print, write, wait, and tail to the scripts in src/completions.zig
- Add 'nu' to 'zmx completions' completions (0a5a2a9)
d0eb4cd
Eric Bower  ·  2026-07-13
chore: use debian
e05515b
Eric Bower  ·  2026-07-13
fix: flaky bats tests
feced06
Eric Bower  ·  2026-07-13
docs: update changelog
050a330
Kai Fronsdal  ·  2026-06-10
fix(kill): release socket before grace sleep so name is reusable

`zmx kill X; zmx run X` raced: the daemon's shutdown defer ran
handleKill() first and only then closed the listen socket and unlinked the
socket file.

Two changes:

- Reorder the shutdown defer so the listen socket is closed and the
  socket file unlinked before handleKill()'s 500ms grace period.
  
- Make `zmx kill` synchronous: after sending .Kill, drain-read the
  connection until EOF. The daemon closes client fds after unlinking
  the socket file, so when `kill` returns the name is guaranteed
  free. Avoids a client-side deleteFile, which would race with a
  fresh session created in between.

Adds test/kill_run_race.bats which fails on main and passes here.
fbbc6be
Vladimir Varankin  ·  2026-05-20
chore: store logs in XDG_STATE_HOME

Previously we were storing logs inside of our socket_dir technically worked
but according to the XDG Base Directory Spec it should be stored inside of STATE.

> The $XDG_STATE_HOME contains state data that should persist between (application)
> restarts, but that is not important or portable enough to the user that it
> should be stored in $XDG_DATA_HOME. It may contain:
>  - actions history (logs, history, recently used files, …)
>  - current state of the application that can be reused on a restart
>    (view, layout, open files, undo history, …)

Reference: https://specifications.freedesktop.org/basedir/latest/#variables
30a153e
Jonas Lergell  ·  2026-07-13
Update zig2nix lock generation (#195)
f7df947
Eric Bower  ·  2026-07-11
chore: upgrade libghostty
96a2aea
Martin Häcker  ·  2026-07-08
Fix darwin build (#190)

This modifies the build so the flake builds correctly on darwin.

`nix flake show` still creates an IFD in the zig2nix flake, but that was
a problem before and could be solved by setting pname and version in the
package - so nothing that got worse with this patch.
6fabec0
Eric Hanchrow  ·  2026-06-28
Suggest the `-t` option to ssh ... (#184)

... when running without config.
b9e135f
Arthur Heymans  ·  2026-06-28
doc: Add emacs-term-sessions to list of community tools (#170)
7381df3
이영수  ·  2026-06-28
fix: forward real terminal pixel size instead of hardcoding 0x0 (#174)

ipc.Resize only carried rows/cols, and getTerminalSize() dropped the
ws_xpixel/ws_ypixel fields read from TIOCGWINSZ. The daemon's three
TIOCSWINSZ call sites (spawnPty, handleInit, handleResize) then hardcoded
ws_xpixel/ws_ypixel to 0 on the inner pty, regardless of what the real
terminal reported.

This breaks Kitty graphics-protocol image sizing for anything running
inside a zmx session, since image dimensions in cells get computed from
a pixel-per-cell ratio that is always 0. nvim plugins like snacks.nvim
report `{cell}: 0 x 0 pixels` and scale images to nothing before sending
them to the terminal.

Add xpixel/ypixel to ipc.Resize, populate them from the real
ioctl(TIOCGWINSZ) result on the client side, and thread them through to
the daemon's TIOCSWINSZ calls instead of hardcoding 0.
4e2d02b
Simon Hartcher  ·  2026-06-28
fix: build ghostty as libghostty-vt only to drop iOS SDK requirement (#181)

zmx only needs the ghostty-vt module, but b.dependency("ghostty", ...)
runs ghostty's full build(), which constructs an XCFramework and resolves
the iOS SDK at configure time via LibCInstallation.findNative. That fails
on machines with only the Command Line Tools installed (no iOS SDK), with
error.DarwinSdkNotFound.

Pass emit-lib-vt=true and emit-xcframework=false at all three ghostty
dependency call sites so ghostty builds the VT library only and skips the
macOS app / iOS XCFramework graph. emit-xcframework must be forced off
because in lib-vt mode it defaults to whether xcodebuild is on PATH, which
is true even with the (non-functional) Command Line Tools stub.
4c2924c
Eric Bower  ·  2026-06-19
perf: reduce allocations in the hot-path of the pty-proxy
79169a0
Eric Bower  ·  2026-06-12
chore(ci): upload static site on main
af94b80
Eric Bower  ·  2026-06-13
chore(ci): generate homebrew file
609f4e3
Scott Abernethy  ·  2026-06-18
fix(docs): correct zmx-select field parsing in session picker (#172)

The zmx-select snippet stripped field prefixes (session_name=,
started_in=) that don't match the actual `zmx list` output, so the
session name displayed as "name=test" instead of "test" and attach
failed. Use glob strips (#*name=, #*start_dir=) which match the real
prefixes and also swallow the leading "→ "/"  " current-session marker.

Fixes #153

Co-authored-by: freqyfreqy <freqyfreqy@users.noreply.github.com>
df2a09b
Mirri  ·  2026-06-18
update README.md for readability (#173)
ba77064
Eric Bower  ·  2026-06-12
fix: dev should use_lld only for linux builds
d469b2e
Eric Bower  ·  2026-06-12
chore(ci): cleanup
d4d5f2a
Eric Bower  ·  2026-05-31
fix(ci): step misconfigurations
364b6a6
Eric Bower  ·  2026-05-29
docs: changelog
769334b
Eric Bower  ·  2026-05-26
chore(ci): dockerizing main release steps
adb11d6
Eric Bower  ·  2026-05-21
refactor: strip ansi escape codes for tail cmd

refactor: pass program env vars to help prevent
a25e2fe
Eric Bower  ·  2026-05-20
refactor(run): marker for heredoc

For most commands we can append `; $?` except for heredoc where the marker
needs to be on its own line.
d5c6926
Eric Bower  ·  2026-06-12
docs: nu completions
b0ae9d6
Eric Bower  ·  2026-06-12
docs: zmx version
2e2b64b
Eric Bower  ·  2026-06-12
docs: help cmd
94738e5
Eric Bower  ·  2026-06-12
docs: readme
ce66992
Jonas Lergell  ·  2026-06-01
fix(run): resolve task bash from PATH (#164)
9889a13
Eric Bower  ·  2026-05-20
fix(run): exit marker formatting cleanup
d588268
Eric Bower  ·  2026-05-20
refactor(run): when creating session will always run `/bin/bash`

BREAKING CHANGE: `zmx run` now requires the target shell to support `$?` exit status tracking
bcf57a1
Eric Bower  ·  2026-05-20
fix: zig build linker error

References: https://codeberg.org/ziglang/zig/issues/31272
8f1b215
Eric Bower  ·  2026-05-16
docs: update version links
6084a4e
Eric Bower  ·  2026-05-16
chore: update zmx version v0.6.0
e023199
Eric Bower  ·  2026-05-16
fix: needs to build on mac as well
34446b7
Eric Bower  ·  2026-05-16
docs: changelog
678a0e3
Eric Bower  ·  2026-05-11
chore: add zls to mise
0ba19cd
Amir B.  ·  2026-05-16
fix: handle run help flag (#152)

Extends the --help/-h check to all subcommands that accept a session
name or other positional args, so users don't accidentally create a
session named "--help" or trigger confusing errors.

Covers: list, completions, history, attach, send, print, write,
kill, wait, tail.
a28c87a
Eric Bower  ·  2026-05-16
chore: set default width to 160

This is mainly so `zmx run` + `zmx history` isn't so truncated
0f94881
Eric Bower  ·  2026-05-16
chore(wait): when looping tasks only print the non-completed tasks
e2a707a
Eric Bower  ·  2026-05-16
chore(wait): continue polling every 1s but only print waiting status every 5s
9d1e6dd
Eric Bower  ·  2026-05-16
feat(wait): print last 20 lines of session history on failure
936c1fa
Eric Bower  ·  2026-05-16
fix: statically link against musl
0a5a2a9
Benjamin Pollack  ·  2026-05-12
feat(completions): add nushell
250c980
Eric Bower  ·  2026-05-16
fix: mise polluting workspace ci runs
cbb53d5
Eric Bower  ·  2026-05-16
fix(ci): dont volume mount the pwd so we can remove mise
04d0b00
Eric Bower  ·  2026-05-16
chore: delete mise so it doesn't bomb from not `mise trust` in tmp workspace
5d7aadc
Eric Bower  ·  2026-05-09
feat: pico.sh ci integration
7ef2be4
bitbloxhub  ·  2026-05-12
fix(nix): update zig2nix so that zmx builds on Lix (#155)
933bb71
Oleg Tarasov  ·  2026-05-11
fix: zsh completion autoload registration (#154)
ca701f8
Justin Su  ·  2026-05-03
Remove aliases from fish subcommand completions (#149)

Fixes #148
Partially reverts #90
e55ccde
Jonas Lergell  ·  2026-05-03
chore: update zig2nix lock generation (#150)
c1c7f7b
Eric Bower  ·  2026-04-29
feat(wait): summary of failed tasks and actionable commands to run

This simply prints the failed tasks and shows some commands to run to see what
went wrong.
0ce812c
Eric Bower  ·  2026-04-29
chore: update pico.sh
1b62815
Eric Bower  ·  2026-04-28
refactor(attach): reset outer terminal to default state

Closes: https://github.com/neurosnap/zmx/issues/106
5389d30
Eric Bower  ·  2026-04-26
feat: cross-compile from linux to mac

Latest version of libghostty now supports cross-compiling without needing
Apple sdk tools.
b79c8c7
Eric Bower  ·  2026-04-26
chore: update libghostty
129875f
Radosław Stachowiak  ·  2026-04-26
feat: accept ls as list alias to help our muscle memory
50db1df
Eric Bower  ·  2026-04-26
docs: copy and changelog
9a33769
Eric Bower  ·  2026-04-26
chore: cleanup
085fe3e
Ian Tay  ·  2026-04-10
refactor(ipc): freeze Tag wire values; switch via connectSession
e889a18
Ian Tay  ·  2026-04-10
fix(daemon): self-pipe signal wakeup + version-tolerant IPC liveness

Self-pipe (idle daemon was deaf to SIGTERM/SIGWINCH):
std.posix.poll loops on .INTR internally (PollError has no Interrupted
member), so the prior `catch error.Interrupted` was unreachable since
8640be51/690487b — signals only "worked" when unrelated I/O woke poll().
Replace with the self-pipe trick: posix.pipe2(.{CLOEXEC,NONBLOCK}), one
wakeSignalPipe handler with errno save/restore, pipe read-end at fixed
poll_fds[2] in both loops; resize/shutdown act in the drain branch.
Removes the now-redundant sigwinch/sigterm atomic flags and collapses
setupSig*Handler into installWakeHandler(sig).

IPC versioning (Option E — decouple liveness from Info shape):
connectSession() does connect-only liveness; 5 of 6 callers (kill,
detach, history, run, attach) switch to it so they survive Info struct
changes. probeSession keeps the Info round-trip for `list`, which now
reports InfoSizeMismatch instead of Unexpected on version skew.

Hygiene: clients_len usize->u64 (extern struct); handleInfo zero-inits
Info so asBytes() doesn't ship 7B tail padding + cmd/cwd stack tails.
Tests freeze @sizeOf(Info)=552 / @sizeOf(Header)=8 and assert zeroed
Info ships zero padding; wired via test{_=ipc;}.
cd82c84
Eric Bower  ·  2026-04-25
docs: pi-zmx
c654778
Eric Bower  ·  2026-04-25
fix(run): ignore DA queries when no terminal client connected

`run` is not a real terminal client, it just tails the output and cannot
respond to shell QA queries so we have a special flag that ignores read-only
clients
758a137
Eric Bower  ·  2026-04-24
refactor(run): check pty foreground process to detect shell

This removes the need to provide the `--fish` flag at all for `zmx run` commands.
5ebf61b
Eric Bower  ·  2026-04-23
feat: print cmd

This sends text directly to client's stdout as-is.  Typically you'll want to wrap
the text in '\r\n' but we leave that up to the end-user
4164523
Pavel Borzenkov  ·  2026-04-24
fix(daemon): reset leader fd on client disconnect (#141)

Make sure leader fd is reset when the leader client disconnects abruptly
without going through 'Detach' IPC. Otherwise, the daemon may remember
wrong client fd as the leader and run all input from the client via
libghostty parser.

Fixes #135

NOTE: this currently doesn't address the issue when libghostty can't
parse the byte sequence and complains like this:

[warning] (parser): CSI colon or mixed separators only allowed for 'm'
command, got: terminal.Parser.Action{ .csi_dispatch = .{ .intermediates
= {  }, .params = { 57444, 1, 1 }, .params_sep = .{ .mask = 2 }, .final
= 117 } }
6feca74
Jay Botte  ·  2026-04-21
feat: send raw bytes to pty input

This is a wrapper around the `.Input` event.

Send delivers bytes to the PTY exactly as-is with no automatic
carriage return. The caller is responsible for appending \r when
shell command submission is desired.
3750464
Eric Bower  ·  2026-04-21
feat: zig build test-integration
c6f3ccb
Eric Bower  ·  2026-04-21
chore: remove debug test file
6e07f1a
Eric Bower  ·  2026-04-21
chore: add zig to mise toml
b18b8f7
Eric Bower  ·  2026-04-21
refactor(test): use `run -d` in tests and detect shell
d63cf5b
x1f9  ·  2026-04-02
test: add BATS session lifecycle tests and mise.toml

14 tests covering session creation, listing, killing, history,
wait, ZMX_DIR isolation, and rapid churn. These tests create real
daemon processes — without the inherited-FD close fix, every test
that calls `zmx run` would hang indefinitely.

mise.toml declares bats 1.13.0 as a dev dependency, establishing
the pattern for managing project tooling.
e70e8b2
x1f9  ·  2026-04-02
fix(daemon): redirect stdio to /dev/null after fork

The existing FD close loop (3-63) handles bats' internal file
descriptors, but bats' `run` keyword captures output via pipes
on FDs 0-2. The daemon inherits these pipe write ends and holds
them open, so bats never gets EOF and hangs.

Redirect stdin/stdout/stderr to /dev/null right after setsid().
The daemon communicates exclusively via its unix socket — it never
reads stdin or writes stdout/stderr. This is standard daemon
hygiene and completes the inherited-FD fix.
efd35ac
ikma  ·  2026-03-28
fix(daemon): close inherited file descriptors after fork

After the daemon forks and re-initializes its own log file, close all
file descriptors from 3 to 63 (excluding server_sock_fd). These FDs
are inherited from the parent process and are not needed by the daemon.

Without this fix, test harnesses like bats hang indefinitely. Bats uses
FDs 3+ internally and waits for them to close before exiting. Since zmx
forks a long-lived daemon that inherits these FDs, bats never sees them
close and blocks forever.

The close happens after log_system.deinit()/init() to avoid closing the
parent's log FD before it's properly released, and before spawnPty() so
the PTY FD (allocated after this point) is not affected.

Uses std.c.close() (raw libc) instead of std.posix.close() to avoid
panicking on already-closed or invalid FDs.
a74702a
Eric Bower  ·  2026-04-21
style: index page
81243b9
Yi Zhou  ·  2026-04-16
Update fish completions for zmx v0.5.0 (#134)
66d1b8b
Eric Bower  ·  2026-04-16
style: site change
5a761ab
Eric Bower  ·  2026-04-16
docs(readme): copy
9d01661
Eric Bower  ·  2026-04-16
chore: prep for v0.5.0
87dc8d1
Eric Bower  ·  2026-04-16
fix: default to bash when running command
24305c0
Eric Bower  ·  2026-04-16
fix(write): remove bracketed paste

Some shell environments don't support it and we are only sending 1 line at a time
so we should be okay to remove it.
12d7505
Eric Bower  ·  2026-04-16
docs: copy and cleanup
7ce50ee
Eric Bower  ·  2026-04-14
fix: add logo back
096ab1d
Eric Bower  ·  2026-04-14
refactor: doc site
911f789
Eric Bower  ·  2026-04-14
docs(readme): paste help msg
538ed39
Eric Bower  ·  2026-04-14
docs: changelog updates
3a901e0
Eric Bower  ·  2026-04-14
refactor: require "*" suffix for wildcard matches

This applies to: wait, kill, and tail

Examples:

- zmx wait "dev*"
- zmx kill "dev*"
- zmx tail "dev*"
- zmx kill "*"
e719274
Eric Bower  ·  2026-04-10
feat: tail, write, and run -d

I'm calling this the ai portal integration.  These features allow a local code
agent to be fully operational against a zmx session that is remote.

Everything works by sending key strokes directly into the zmx session.

This means it doesn't matter where the remote session lives it should work
as if you were typing the commands yourself.  SSH'd into a server? Inside
a container?  It's all the same to zmx and the code agent.

BREAKING CHANGE: `zmx run` is now synchronous by default *and* immediately
tails the session output so the agent can get immediate feedback.  To use the
previous run behavior where the run command completes immediately use detached
mode:
  `zmx run -d`
bbbe245
Shravan Sunder  ·  2026-04-14
fix: rewrite OSC 133;A with redraw=0 to prevent prompt loss on resize (#112)

When zmx forwards shell output containing OSC 133;A (prompt start)
to the outer terminal, the terminal sets shell_redraws_prompt=true.
On resize, the terminal clears prompt rows expecting the shell to
redraw them. But the shell's redraw goes through zmx's IPC relay
chain with cursor coordinates relative to the inner PTY state,
causing a cursor desync that makes the prompt invisible.

Fix: rewrite OSC 133;A to include redraw=0 before forwarding. This
tells the outer terminal that the process on its PTY (zmx client)
cannot redraw prompts, so it should leave prompt rows intact on
resize. This is a standard Kitty protocol extension.

Also disables shell_redraws_prompt on the daemon's internal
ghostty_vt terminal during resize to prevent snapshot state
corruption.

Fixes #111. Likely also fixes #99.
6eea0f6
Eric Bower  ·  2026-04-12
chore: dont show task_command because it is unreliable

We just don't have a great way to send [][]u8 over ipc because
we are using structs with fixed size fields that are converted to/from
via std.mem.asBytes.  This means we have to convert the command from [][]u8
to []u8 but that makes our daemon struct fields complicated.

Instead, just rely on looking at the zmx history to understand the command
that was run.
fbf0171
Adrian  ·  2026-04-13
feat: allow configuring socket and log permissions via environment variables (#130)
5f30ba8
Eric Bower  ·  2026-04-09
docs(readme): demo video
b610ddd
Eric Bower  ·  2026-04-09
refactor: set leader by detecting user input with libghostty parser
af0e1f3
Eric Bower  ·  2026-04-08
chore: cleanup util formatting
3971f2d
Michael Sakaluk  ·  2026-03-18
fix: two-phase terminal state serialization for nested session cursor corruption

Fixes #31 (cursor position corruption on re-attach over SSH).
Partially addresses #86 (cursor style error after attach).
Contributes to #96 (testing framework).

Problem:
When running nested zmx sessions (zmx→SSH→zmx), re-attaching to the outer
session causes cursor positions and screen content to appear at wrong rows.
The root cause is that serializeTerminalState() writes scrollback + visible
content sequentially. Scrollback lines scroll the terminal before CUP
sequences arrive, shifting all visible content by the scrollback overflow.

Fix:
Split serialization into two phases:
1. Phase 1: Emit scrollback content only (no modes/cursor/keyboard).
   These lines scroll into the terminal's scrollback buffer.
2. Clear visible screen (ESC[2J ESC[H ESC[0m) to reset for phase 2.
3. Phase 2: Emit visible screen only with full extras (modes, cursor,
   keyboard, scrolling region).

The clear between phases ensures visible content starts from a clean slate
regardless of how much scrollback preceded it. Scrollback is preserved in
the terminal's buffer for native scroll-up. This approach is inspired by
tmux's visible-only redraw strategy but preserves terminal scrollback.

Tests:
Added 7 integration tests using ghostty-vt roundtrip verification:
- Cursor position preservation after roundtrip
- CUP-positioned markers survive roundtrip
- Scrollback does not shift visible content (the core bug)
- Nested double-roundtrip (inner→outer→client)
- Alternate screen content not leaked
- Terminal size mismatch (30→24 rows) + roundtrip
- Scrollback + size mismatch + nested roundtrip (stress test)

All tests run in <1 second via `zig build test`.
3df5416
Eric Bower  ·  2026-04-08
docs(changelog): switching sessions
99c40d3
Eric Bower  ·  2026-04-08
chore: use env var fn for session name
43e6f0f
Eric Bower  ·  2026-04-03
feat(attach): support switching sessions

Previously we did not allow users to switch to a session from within a session
via `zmx attach`.

Now users are able to run `zmx attach` from within a session and it'll properly
detach and then reattach to the new session.

References: https://github.com/neurosnap/zmx/issues/91
66c8e0e
Eric Bower  ·  2026-04-08
feat: change pty window size when new leader is set

When we set a new client leader we send a Resize event request from the daemon
to the client.  So now whenever a user types into their stdin we set the new leader
and then resize the window.
bdbf795
Eric Bower  ·  2026-04-05
chore(leader): init cmd only resize if leader
87f89de
Eric Bower  ·  2026-04-04
fix: kitty up arrow detection
c6956f8
Eric Bower  ·  2026-04-04
chore(leader): up arrow can claim client leader
a65e500
Eric Bower  ·  2026-04-04
fix(util): tests
08a06c0
Eric Bower  ·  2026-04-04
refactor: kitty key press logic to be more generic

fix: src/util.zig tests were not being run :sad:
d0e3ed1
Eric Bower  ·  2026-04-03
refactor: generic parsing kitty ctrl key fns
e9729b9
Eric Bower  ·  2026-04-03
docs: leader comment
d48b642
Eric Bower  ·  2026-04-03
feat: new client leader policy

The last client to send user input bytes (non-ansi escape codes) becomes the leader. The client
leader controls resizing and any other terminal state changes. Non-leader clients are read-only
until they send user input bytes and takeover leadership.

Closes: https://github.com/neurosnap/zmx/issues/73 
77d03a5
Eric Bower  ·  2026-04-02
feat(kill): add `--force` flag to remove the socket file

If we cannot communicate with the daemon then it could mean a number of things.
We don't automatically assume that a communication error means we can never connect to it.

So we add a `--force` command for when there is an error the user cannot recover from and we
will delete the socket file.

Unfortunately, we do not know the pid for the terminal session outside of the daemon so we
cannot automatically kill the pid in some cases which means it could linger.
cc33276
とーふとふ  ·  2026-03-31
fix(daemon): close pty master before waitpid to prevent zombie on macOS (#114)
1aaf713
Eric Bower  ·  2026-03-31
docs(ai): introduce claude skill
3477fb2
Ian Tay  ·  2026-03-18
feat(daemon): buffer PTY stdin writes and flush via POLLOUT

Replaces the best-effort ptyWrite (drop on EAGAIN) with a buffered
queue flushed by the daemon's poll loop. Mirrors the pattern already
used for client-socket writes.

- pty_write_buf on Daemon, capped at 256KB (drop new payload on
  overflow — same failure mode as before, 64x higher threshold)
- POLLOUT registered on pty_fd when buffer non-empty; flush handler
  loops until EAGAIN
- handleInput/handleRun queue instead of writing directly
- respondToDeviceAttributes routes through the same buffer so DA
  responses can't interleave with a draining .Run payload after
  client disconnect

Follow-up to #82.
98fa966
Eric Bower  ·  2026-03-23
feat(kill): accepts multiple args and matches session prefixes

Examples:
- zmx kill one
- zmx kill one two three
- zmx kill d.
- ZMX_SESSION_PREFIX="d." zmx kill
fac9795
Eric Bower  ·  2026-03-23
chore: dockerfile and ci file

I'm experimenting with using zmx as a job engine for CI.
7cdb333
Eric Bower  ·  2026-03-23
chore: wrap lines where applicable to 100
4dff5d4
Eric Bower  ·  2026-03-23
docs: index file
932e0ed
Eric Bower  ·  2026-03-23
docs: cleanup and rewording

I'm removing `AGENTS.md` mainly because it is not particularly useful and there is research to
suggest these files don't really improve agent performance.
3b0e4e8
Eric Bower  ·  2026-03-22
docs: doc strings
89e2728
Eric Bower  ·  2026-03-22
fix(list): send no session found msg to stderr

Fixes: https://github.com/neurosnap/zmx/issues/101
7785934
Eric Bower  ·  2026-03-19
chore: detach key cleanup
a9dc983
Patrik Sundberg  ·  2026-03-17
fix(attach): reject combined intentional modifiers for Ctrl+\ detach

The parser accepted any modifier combination that included ctrl
(ctrl+shift, ctrl+alt, ctrl+super, etc.) because it only checked
the ctrl bit. This was over-permissive: ctrl+shift+\ is a different
key combination and should not trigger detach.

Tighten the check to require ctrl as the ONLY intentional modifier.
Lock modifiers (caps_lock, num_lock) remain tolerated since they are
ambient state, not deliberate key combinations.
46b02ee
Patrik Sundberg  ·  2026-03-15
fix(attach): handle all kitty keyboard protocol encodings for Ctrl+\

The old detection used rigid substring matching for exactly two
sequences (\x1b[92;5u and \x1b[92;5:1u). This failed when apps
enabled progressive enhancement flags that change the encoding:

- Lock modifiers (caps_lock, num_lock) alter the modifier value
- Alternate key sub-fields add :shifted:base after the key code
- Combined modifiers (ctrl+shift, ctrl+alt, etc.) change the value
- Text codepoint sections append an extra ;codepoints field

Replace with a proper CSI u parser that:
1. Matches key code 92 (backslash) with any alternate sub-fields
2. Checks the ctrl bit in the modifier value: (mod - 1) & 0b100
3. Accepts press/repeat events, rejects release
4. Tolerates optional text codepoint sections
a0c915c
Eric Bower  ·  2026-03-19
docs(readme): mark as unofficial packages
48d9a17
Vishal  ·  2026-03-19
fix: avoid replaying synchronized output on reattach (#95)
27bd9b8
Eric Bower  ·  2026-03-18
chore: prep for 0.4.2 release
19d1b76
Samuel Hautamäki  ·  2026-03-17
Document the new Gentoo overlay. (#94)
1065bfd
Eric Bower  ·  2026-03-13
refactor: move print fn to socket.zig
fb0e621
Michael Sakaluk  ·  2026-03-10
fix: validate session name length against Unix socket path limit

Session names that cause the socket path to exceed the OS sun_path limit
(104 bytes on macOS, 108 on Linux) now produce a clear error message
instead of failing silently with exit code 1.

- Add max_socket_path_len constant derived from platform sockaddr_un
- Add validation in getSocketPath returning error.NameTooLong
- Add maxSessionNameLen helper for dynamic limit computation
- Add printSessionNameTooLong for user-facing error on stderr
- Handle NameTooLong in all command paths (run, attach, kill, history, detach, list)
- Move validation before sessionExists in kill/history so the error
  message is shown even when the socket file doesn't exist
- Add 6 unit tests covering boundary conditions and platform constants

Fixes: https://github.com/neurosnap/zmx/issues/84
f520c4c
Yi Zhou  ·  2026-03-12
Refactor fish completions for zmx command (#90)
fd29a10
Iván Hernández Cazorla  ·  2026-03-12
chore: add openSUSE package link to website (#89)
ecf3857
Michael Sakaluk  ·  2026-03-12
fix: print error to stderr when session does not exist (#88)
49b126a
Eric Bower  ·  2026-03-11
fix(attach): type error
d0683a3
Eric Bower  ·  2026-03-11
chore(history): bare `zmx history` should try to use `ZMX_SESSION`
1fc6306
Ian Tay  ·  2026-03-08
fix(ipc): validate wire data before arithmetic and indexing

- expectedLength: header.len is u32 off the wire; adding sizeof(Header)
  at u32 width could wrap (panic in safe mode, UB slice bounds in
  release). Widen to usize first.
- get_session_entries: cmd_len/cwd_len are u16 (max 65535) but index
  into [256]u8 arrays. Clamp before slicing.
df38b13
Ian Tay  ·  2026-03-08
fix(attach): skip termios setup when stdin is not a TTY

tcgetattr's return value was discarded, so when stdin was piped
(e.g. `zmx attach foo < /dev/null`), orig_termios remained Zig
`undefined` stack bytes. These garbage bytes were then copied,
modified by cfmakeraw, and applied via tcsetattr — UB in safe builds.
4a6604b
Ian Tay  ·  2026-03-08
fix(attach): serialize terminal state before resize, as the comment says

The existing comment correctly explains why serialize must happen before
resize (reflow moves the cursor), but the code did the opposite. Reattaching
with a different terminal size could leave the cursor misplaced.
76cec40
Ian Tay  ·  2026-03-08
fix(pty): isolate forked child from parent code path and heap-alloc argv

Two issues in spawnPty's child (pid==0) branch:

1. `try` on allocPrint/bufPrintZ could propagate an error past the
   if-block, causing the forked child to fall through to the parent
   code path — running a second daemon on the same socket, or hitting
   errdefers that delete the parent's socket file. The bufPrintZ case
   is triggerable via a long SHELL basename.

2. The fixed-size argv_buf[64] overflowed with >63 CLI arguments.

Both are fixed by extracting child setup into execChild() which returns
!noreturn (exec or error), with the caller exiting on any error. The
argv array is now heap-allocated to the exact size needed.
11a12cc
Ian Tay  ·  2026-03-08
fix: only clean up socket on ConnectionRefused, not Timeout

A 1-second probe timeout doesn't mean the daemon is dead — it may just be
busy (heavy output, terminal resize reflow, serializing state for another
client). Deleting a live daemon's socket orphans it permanently with no
way to reach it via zmx commands.

Applied to all callsites: get_session_entries, ensureSession (worst case:
spawns a replacement daemon leaving the old one orphaned), kill, detachAll,
history. For kill, the user now gets a helpful message if the daemon is
busy vs. actually dead. The `zmx list` status label for error entries now
says `status=unreachable` (not `cleaning up`) on Timeout, so the display
doesn't contradict what we actually did.

`zmx wait` now treats is_error entries as done+failed: on Timeout the
socket persists, so without this the session would sit at task_ended_at==0
forever, defeating both the completion check and the zero-match timeout.
690487b
Ian Tay  ·  2026-03-08
fix: signal handling — ignore SIGPIPE, handle EINTR in poll

- Ignore SIGPIPE once in main() (inherited across fork, covers all
  subcommands). Without this, writing to a closed socket delivers
  SIGPIPE (default disposition: terminate) before write() can return
  EPIPE. An abrupt client exit killed the daemon; a daemon that died
  between probe and send killed one-shot clients (run/kill/history).
- Handle EINTR in the daemon's poll loop (clientLoop already does this).
  Without this, SIGTERM/SIGCHLD caused the daemon to exit with an error
  instead of checking the sigterm flag and shutting down gracefully.

Note: SA_RESTART is intentionally NOT set on SIGTERM/SIGWINCH. On BSD/macOS
(unlike Linux), poll() is restartable when SA_RESTART is set — an idle
daemon would never wake from poll() to check the sigterm flag. The EINTR
handling in the poll loop is the correct and sufficient fix.
12a19ee
Ian Tay  ·  2026-03-08
fix: use platform-correct O_NONBLOCK for fcntl; fix PTY write and double-close

The hardcoded value 0o4000 is Linux-specific; on macOS O_NONBLOCK is 0x4.
posix.SOCK.NONBLOCK is for socket()/accept4(), not fcntl(F_SETFL) — it only
worked on Linux by coincidence where both constants share the same value.
On macOS, non-blocking mode was never actually set on the PTY, client
socket, or stdin.

Setting O_NONBLOCK correctly exposes two latent issues, also fixed here:

- PTY writes in handleInput/handleRun did `_ = try posix.write()`,
  discarding short-write counts and propagating WouldBlock as an error
  that crashed the daemon. Added ptyWriteAll() that polls on WouldBlock
  and retries short writes — same blocking semantics macOS had implicitly.

- ensureSession's errdefer + defer both closed server_sock_fd when
  daemonLoop errored, causing EBADF (posix.close treats this as
  unreachable → panic in safe builds). Restructured so spawnPty failure
  is handled by an explicit catch; the defer is the sole owner after.

Additionally, O_NONBLOCK is set on the open file description (shared with
the parent shell), so stdin's original flags must be restored on exit to
avoid leaving the parent shell's stdin in non-blocking mode.
35f56e6
Ian Tay  ·  2026-03-08
fix(wait): time out after 3 polls if no matching sessions are found

Follow-up to the upstream wait fix: the `total > 0` guard prevented
vacuous exit 0, but left a typo'd `zmx wait foo` polling forever —
trading a wrong answer for no answer. After 3 iterations (~3s) with
max_seen still at 0, exit 2 with "no matching sessions found".

3s is generous: `zmx run foo && zmx wait foo` is essentially sequential
(run blocks until the socket exists and the Run IPC is acked), so a
persistent zero is a typo, not slowness.
2d37a09
Iván Hernández Cazorla  ·  2026-03-08
chore: add link to openSUSE Tumbleweed package (#79)
a27b371
Ian  ·  2026-03-08
Fix verified bugs (#83)

* fix: validate session names to prevent path traversal

Session names become filenames under socket_dir. Without validation,
`zmx attach ../foo` would create a socket outside that directory, and
(worse) stale-socket cleanup via unlinkat(dirfd, "../foo") would delete
files outside it. The ZMX_SESSION_PREFIX env var was similarly unsanitized.

Also fixes a minor inefficiency: seshPrefix() was called twice.

* fix(history): don't panic when only flags are provided

`zmx history --vt` panicked on the .? unwrap when no positional session
name was given. Pass empty string instead so getSeshName handles it
(returns error.SessionNameRequired if no ZMX_SESSION_PREFIX is set either).

* fix(wait): don't report vacuous success when no sessions match

total == done was true when both were 0, so `zmx wait foo` returned
exit 0 immediately if no session named foo existed — either because of a
typo, or because of a race where the preceding `zmx run foo` hadn't yet
created its socket. Now wait keeps polling until at least one matching
session is seen.

Also tracks the high-water mark of matched sessions: if the count drops,
a session disappeared (daemon crashed or was killed), so wait errors out
instead of looping forever.

* fix(run): reset task state when receiving a Run command

The daemon's exit-marker scan is gated on task_exit_code == null, but that
field was never reset after the first task completed. A second `zmx run`
on the same session would have its ZMX_TASK_COMPLETED marker ignored,
causing `zmx wait` to immediately return the *first* task's exit code.

Also explicitly set is_task_mode so `zmx run` works against sessions that
were originally created by `zmx attach` (non-task mode).

* fix(run): use CR not LF to submit commands to an already-prompting shell

When `zmx run` sends a command to an existing session, the shell is at
the readline prompt with the PTY in raw mode. readline binds accept-line
to CR (\r), not LF (\n), so a trailing \n just moves the cursor — the
command sits typed but unexecuted.

The first-ever `zmx run` on a fresh session happened to work because
it's sent during shell startup, before readline takes over: the line
discipline is still canonical and ICRNL translates \n. Any subsequent
run silently did nothing, which was masked by the stale-task-state bug
(wait returned the first run's exit code, looking like success).

\r works in both modes: canonical mode translates it via ICRNL, and
raw-mode readline accepts it directly.
ccdc1b9
Danil Agafonov  ·  2026-03-06
docs: add session picker recipe using fzf (#75)

Add a shell function that fuzzy-finds and attaches to zmx sessions,
previews scrollback history, or creates new ones. Includes a Ctrl-N
keybinding to force session creation when a fuzzy match is highlighted.
Particularly useful for remote SSH workflows.
0222428
Eric Bower  ·  2026-03-04
refactor: break up main.zig

This is just some code cleanup to make it a little easier to navigate the codebase.  I don't want to break everything up into separate files but I want the main business logic to live inside of main.zig.
fe2c4ce
Eric Bower  ·  2026-03-04
chore: remove comment about old strat
4d398e8
External.Kai-Fronsdal  ·  2026-03-04
single-quote-escape
fix(run): always single-quote args to avoid bash history expansion bug

The previous shellQuote preferred double quotes, but \! does not
suppress history expansion inside double quotes in interactive bash.
Switch to always using single quotes (matching Python's shlex.quote),
which are universally safe since nothing is special inside single
quotes except ' itself.

Add unit tests for shellNeedsQuoting and shellQuote.

Fixes #72

Made-with: Cursor
b2833e7
Eric Bower  ·  2026-03-03
chore(list): changed key names and made formatting more stable
be4cb72
Eric Bower  ·  2026-03-03
docs: changelog
87cc98f
Eric Bower  ·  2026-03-03
fix(run): stdin regression with ZMX_TASK_COMPLETED

We were not actually running the command sent into stdin properly.

Closes: https://github.com/neurosnap/zmx/issues/71
d12b7cc
Eric Bower  ·  2026-03-03
fix(run): when no client is attached, send DA response query from daemon
954f8db
Eric Bower  ·  2026-03-03
fix(run): re-quote when using shell meta chars

argv doesn't receive the quotes in the command line so we need to apply an algorithm to re-quote args when it uses special meta-characters.

Closes: https://github.com/neurosnap/zmx/issues/72
ac857cf
rok  ·  2026-03-01
fix: fix nix build failure (#69)

73aa243
Eric Bower  ·  2026-02-28
fix(wait): use-after-free
804e4e2
Justin Su  ·  2026-02-27
Use `env_var` module in Starship prompt snippet (#66)

1e4e888
ida  ·  2026-02-26
Add starship shell prompt instructions (#62)

954b392
Justin Su  ·  2026-02-26
Update fish shell instructions to use user completions directory (#64)

a060a8e
Justin Su  ·  2026-02-26
Simplify `brew tap` and `brew install` into a single command (#65)

728beaa
Eric Bower  ·  2026-02-23
chore: publish v0.4.1
09b69c2
Eric Bower  ·  2026-02-23
fix(ghostty): zig build test was failing
1f4d79d
Noelle Leigh  ·  2026-02-22
docs: Fix typo in CHANGELOG

"stale" → "stall"

Signed-off-by: Noelle Leigh <noelle_leigh@fastmail.com>
f783c17
Eric Bower  ·  2026-02-22
docs: add alpine pkg
694699e
Eric Bower  ·  2026-02-22
docs: site
103b62b
Eric Bower  ·  2026-02-22
docs: readme

new install.packages section
9db69ec
L.B.R.  ·  2026-02-21
fix(terminal): pop Kitty keyboard protocol on detach (#59)

48520f9
Eric Bower  ·  2026-02-20
docs: readme
5ae3cfb
Eric Bower  ·  2026-02-20
chore: publish v0.4.0
fc848b0
Eric Bower  ·  2026-02-20
chore: update libghostty-vt
8cf33e1
Eric Bower  ·  2026-02-20
chore: nanoTimestamp -> timestamp
58eb669
Eric Bower  ·  2026-02-20
docs: changelog
e5b9dcd
L.B.R.  ·  2026-02-20
docs: add zmosh to community tools (#58)

Co-authored-by: L.B.R. <lbr@mmonad.com>
894eced
Eric Bower  ·  2026-02-20
docs: readme
714641d
Eric Bower  ·  2026-02-20
docs: readme
770481a
L.B.R.  ·  2026-02-20
fix: add missing fields to writeSessionLine test (#56)

08b33dc
Eric Bower  ·  2026-02-19
chore(wait): add `w` alias
595690f
Eric Bower  ·  2026-02-19
docs: readme
fd0874e
Eric Bower  ·  2026-02-19
feat: wait command

This command will wait for all tasks to be completed and return an "aggregate" exit code.
15c4a9c
Eric Bower  ·  2026-02-17
feat: env var `ZMX_SESSION_PREFIX`

This change will allow users to set an environment variable that prefixes all session names with
the value provided in the env var.  This should help with some automation tasks relying on zmx since
you can now "group" sessions together under a common prefix.
fbcec11
Eric Bower  ·  2026-02-08
feat(run): track task completion with a marker and record exit code
969ef90
Eric Bower  ·  2026-02-19
docs: readme
66c7fa1
Eric Bower  ·  2026-02-19
docs: readme
9b3b44a
Michael Sakaluk  ·  2026-02-19
docs: add zsm to community tools section in README (#55)

5663cb9
Eric Bower  ·  2026-02-18
chore: new logo
75bd1b4
Eric Bower  ·  2026-02-18
chore: new logo
e3f5d96
Eric Bower  ·  2026-02-16
feat(version): print socket and log directory locations
e21e726
wegel  ·  2026-02-12
chore: update ghostty to HEAD (#53)

7a30c62
David Calhoun  ·  2026-02-02
docs: Fix typo in feature list on index.html (#51)

bc17397
Eric Bower  ·  2026-02-01
docs: readme
0dae6cf
Eric Bower  ·  2026-02-01
chore: publish v0.3.0
98995bd
Eric Bower  ·  2026-02-01
chore: short should be session name only
79fce5d
Paul Smith  ·  2026-01-24
feat: indicate current session in listing

This change adds a visual indicator (a prefixed arrow) of the current
session, if any, to session listings with `zmx [l]ist`.

Example:

```
$ zmx l
  session_name=o.quux	pid=38719	clients=0	started_in=/home/example
→ session_name=o.foo.bash	pid=60775	clients=2	started_in=/home/example
  session_name=o.foo.quux	pid=50707	clients=1	started_in=/home/example
  session_name=o.zmx	pid=21531	clients=2	started_in=/home/example
  session_name=o.zmx.bash	pid=17772	clients=1	started_in=/home/example
$ zmx l --short
  o.quux
→ o.foo.bash
  o.foo.quux
  o.zmx
  o.zmx.bash
```

If there is no current session, the output is unchanged.

Fixes #42.
a32b80d
Eric Bower  ·  2026-01-23
docs: completions check if cmd exists first
b2fc56b
Eric Bower  ·  2026-01-23
docs: copy
f062076
Josh Bainbridge  ·  2026-01-01
docs: add documentation for evaluating auto-complete

After adding support for auto-completion, we could add instructions for
users on how to integrate this into their shell configurations.

Update the README with a new sections for shell auto-complete
configuration.
3e5f982
Josh Bainbridge  ·  2026-01-01
feat: add completions command to output shell completion scripts

Adding auto-completion support for a limited number of commonly used
shells (bash, zsh, fish) would allow for greater shell integration. This
can be done by using completions command that is then evaluated in the
shell config.

Add a new completions module that holds completion scripts for zsh, bash
and fish. Then include a new command called completions that takes a
shell name and outputs the requested script.

This can then be added to a shell config such as .zshrc like so:

eval “$(zmx completions zsh)"

For automatic shell integration.
68dfbef
Josh Bainbridge  ·  2026-01-01
feat: add new short list option via flag

Listing just the session names, without extra information, could be
useful for external tools or processes that need to do operations based
on the session names.

Add a new --short option to the list subcommand to list only session
names without extra information.
314974e
Eric Bower  ·  2026-01-23
chore(version): show git sha if running dev build
1dbbe3d
Eric Bower  ·  2026-01-23
feat(list): show `started_in` directory and original `cmd` if provided
e17899d
Eric Bower  ·  2026-01-21
fix: properly handle killing shells and children

Shell's typically ignore SIGTERM unless there's a trap installed, but they will respond to SIGHUP.
This works fine for shells but when a user runs `zig attach editor nvim` or any other non-shell command
they might not respond to SIGHUP the way we want.  So we have implemented a multi-signal approach:

- SIGHUP
- Wait 500ms
- SIGKILL

Reference: https://github.com/shell-pool/shpool/blob/1b3b27f059f0008c0509b5170d84b86c752b1da3/libshpool/src/daemon/shell.rs#L79-L94
Reference: https://github.com/neurosnap/zmx/pull/47
377c062
Eric Bower  ·  2026-01-20
fix: gracefully shutdown daemon process

This change properly handles when the daemon process receives a SIGTERM.

Also during the kill process we properly forward SIGTERM to all children processes of the daemon.
6e6bc9c
Eric Bower  ·  2026-01-09
docs(readme): copy