fyi: this issue was filed with Claude Code. I reviewed Claude's analysis when I ran into the bug and it made sense.
Problem
zmx list probes each session's Unix socket one at a time. When a session is responsive this takes microseconds, but when a daemon is unreachable it blocks for the full connect timeout (~1s). The timeouts accumulate linearly:
# 3 unreachable sessions → 3s wall-clock time
$ time zmx list --short
buffer-skill
frag-310
...
zmx list --short 0.00s user 0.01s system 0% cpu 3.020 total
# After killing the 3 stale daemons → 11ms
$ time zmx list --short
buffer-skill
frag-310
...
zmx list --short 0.00s user 0.00s system 54% cpu 0.011 total
This matters because zmx list is the foundation for fzf-based session pickers (like the recipe in the docs). A 3-second pause before the picker appears makes the UX feel broken, especially since the user has no way to know stale sessions are the cause.
How sessions become stale
In my case, 3 daemons were alive (held open sockets, visible in lsof) but completely unresponsive — they ignored both zmx kill and SIGTERM, only SIGKILL removed them:
$ zmx list 2>/dev/null | grep Timeout
name=threading-prs err=Timeout status=unreachable
name=site-analytics err=Timeout status=unreachable
name=karabiner-hotkey-apps err=Timeout status=unreachable
$ zmx kill karabiner-hotkey-apps
session karabiner-hotkey-apps is unresponsive (Timeout) -- daemon may be busy, try again or kill the process directly
$ kill 76207 22112 11266 # SIGTERM — ignored
$ kill -9 76207 22112 11266 # SIGKILL — finally works
These were all zmx attach sessions where the Ghostty terminal was closed (the daemons had no controlling terminal — ?? in ps). I suspect the daemon entered a state where it's blocked on a dead PTY or pipe and can't service the IPC socket.
Proposed fix: probe sessions concurrently
If zmx list probed all sockets in parallel (e.g., one thread/coroutine per session), the worst case becomes max(timeouts) instead of sum(timeouts). With 3 stale sessions that's 1s instead of 3s — and with many sessions, the improvement is proportionally larger.
This would also improve the UX of ctrl-r:reload(zmx list --short) inside fzf pickers, which currently freezes the picker for the full timeout duration.
Bonus: --skip-unreachable flag
A zmx list --skip-unreachable flag (or --reachable-only) that omits timed-out sessions from output would let fzf pickers show only actionable sessions without waiting for timeouts at all. This could be built on top of parallel probing or independently.
Relationship to existing issues/commits
kill --force addresses manual cleanup after the fact, but doesn't help zmx list performance — you'd need to already know which sessions are stale and kill them before running list. Parallel probing solves the problem at the source.
Environment
- zmx 0.4.2 (Homebrew)
- macOS 15.5 (Darwin 25.4.0), Apple Silicon (arm64)
- Ghostty 1.3.0-dev
- 14 active sessions at time of diagnosis, 3 unreachable
fyi: this issue was filed with Claude Code. I reviewed Claude's analysis when I ran into the bug and it made sense.
Problem
zmx listprobes each session's Unix socket one at a time. When a session is responsive this takes microseconds, but when a daemon is unreachable it blocks for the full connect timeout (~1s). The timeouts accumulate linearly:This matters because
zmx listis the foundation for fzf-based session pickers (like the recipe in the docs). A 3-second pause before the picker appears makes the UX feel broken, especially since the user has no way to know stale sessions are the cause.How sessions become stale
In my case, 3 daemons were alive (held open sockets, visible in
lsof) but completely unresponsive — they ignored bothzmx killandSIGTERM, onlySIGKILLremoved them:These were all
zmx attachsessions where the Ghostty terminal was closed (the daemons had no controlling terminal —??inps). I suspect the daemon entered a state where it's blocked on a dead PTY or pipe and can't service the IPC socket.Proposed fix: probe sessions concurrently
If
zmx listprobed all sockets in parallel (e.g., one thread/coroutine per session), the worst case becomesmax(timeouts)instead ofsum(timeouts). With 3 stale sessions that's 1s instead of 3s — and with many sessions, the improvement is proportionally larger.This would also improve the UX of
ctrl-r:reload(zmx list --short)inside fzf pickers, which currently freezes the picker for the full timeout duration.Bonus:
--skip-unreachableflagA
zmx list --skip-unreachableflag (or--reachable-only) that omits timed-out sessions from output would let fzf pickers show only actionable sessions without waiting for timeouts at all. This could be built on top of parallel probing or independently.Relationship to existing issues/commits
zmx clean/prunefor stale sockets; closed in favor ofkill --forcezmx killon macOS #113 / PR fix(daemon): close pty master before waitpid to prevent zombie on macOS #114 — fixed the zombie-on-kill bug (pty/waitpid ordering on macOS); mergedzmx kill --forceto delete sockets for unresponsive daemons; on main but not yet in a releasekill --forceaddresses manual cleanup after the fact, but doesn't helpzmx listperformance — you'd need to already know which sessions are stale and kill them before runninglist. Parallel probing solves the problem at the source.Environment