Skip to content

zmx list probes sessions sequentially — O(n) slowdown from unreachable sessions #132

Description

@kaushikgopal

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions