Summary
Three tests in tests/test_server.py fail when a tmux server happens to be alive at the exact socket path they hardcode. Because the tests register no cleanup, any daemon that ends up at one of those socket names — from a prior crashed run, manual debugging, or anything else — sticks around and breaks them on every subsequent run, even in isolation.
The library code (Server.is_alive, Server.cmd, Server.raise_if_dead, tmux_cmd) is correct. This is purely a test-isolation defect.
Affected tests (tests/test_server.py)
def test_no_server_sessions() -> None:
server = Server(socket_name="test_attached_session_no_server")
assert server.sessions == []
def test_no_server_attached_sessions() -> None:
server = Server(socket_name="test_no_server_attached_sessions")
assert server.attached_sessions == []
def test_no_server_is_alive() -> None:
dead_server = Server(socket_name="test_no_server_is_alive")
assert not dead_server.is_alive()
def test_raise_if_dead_no_server_raises() -> None:
dead_server = Server(socket_name="test_attached_session_no_server")
with pytest.raises(subprocess.CalledProcessError):
dead_server.raise_if_dead()
(Note: test_no_server_sessions and test_raise_if_dead_no_server_raises share the same hardcoded socket name test_attached_session_no_server — a copy-paste leftover that matches neither function name.)
Reproduction
$ tmux -L test_no_server_is_alive new-session -d
$ uv run pytest tests/test_server.py::test_no_server_is_alive -v
Result:
tests/test_server.py:210: in test_no_server_is_alive
assert not dead_server.is_alive()
E assert not True
Same pattern reproduces for the other two:
$ tmux -L test_attached_session_no_server new-session -d
$ uv run pytest tests/test_server.py::test_no_server_sessions tests/test_server.py::test_raise_if_dead_no_server_raises
Root cause
- The tests construct
Server with hardcoded socket_name strings.
- They register no finalizer, so anything that creates a daemon at
/tmp/tmux-<uid>/<socket_name> persists indefinitely.
- tmux does not always
unlink(2) its socket on non-graceful exit, so leftover socket files accumulate in /tmp/tmux-<uid>/.
- A later test run's
Server(socket_name=...).is_alive() correctly reports True because the daemon really is alive — the test's assumption that the name is unused is the wrong premise.
In my environment two such daemons had been listening since 3 days prior, with PIDs 984553 and 984791, on /tmp/tmux-1000/test_attached_session_no_server and /tmp/tmux-1000/test_no_server_is_alive respectively. lsof -U | grep tmux confirmed live LISTEN sockets on both paths.
Proposed fix
Use the existing server fixture (src/libtmux/pytest_plugin.py:144-182) — it already produces a Server with a unique random socket name (libtmux_test{N}) and registers a _reap_test_server finalizer that kills any daemon and unlinks the socket file. A freshly-created Server with a unique name has no daemon running on it, so it is the "dead server" these tests need.
def test_no_server_sessions(server: Server) -> None:
assert server.sessions == []
def test_no_server_attached_sessions(server: Server) -> None:
assert server.attached_sessions == []
def test_no_server_is_alive(server: Server) -> None:
assert not server.is_alive()
def test_raise_if_dead_no_server_raises(server: Server) -> None:
with pytest.raises(subprocess.CalledProcessError):
server.raise_if_dead()
This eliminates every hardcoded socket name, removes the typo coupling, and inherits cleanup automatically.
Workaround for affected users
$ tmux -L test_attached_session_no_server kill-server 2>/dev/null
$ tmux -L test_no_server_is_alive kill-server 2>/dev/null
$ tmux -L test_no_server_attached_sessions kill-server 2>/dev/null
$ rm -f /tmp/tmux-$(id -u)/{test_attached_session_no_server,test_no_server_is_alive,test_no_server_attached_sessions}
Environment
- libtmux: master @ 4e19628 ("py(deps[dev]) Bump dev packages")
- Python 3.10+
- tmux: system tmux on Linux 6.6.87.2-microsoft-standard-WSL2
Summary
Three tests in
tests/test_server.pyfail when a tmux server happens to be alive at the exact socket path they hardcode. Because the tests register no cleanup, any daemon that ends up at one of those socket names — from a prior crashed run, manual debugging, or anything else — sticks around and breaks them on every subsequent run, even in isolation.The library code (
Server.is_alive,Server.cmd,Server.raise_if_dead,tmux_cmd) is correct. This is purely a test-isolation defect.Affected tests (
tests/test_server.py)(Note:
test_no_server_sessionsandtest_raise_if_dead_no_server_raisesshare the same hardcoded socket nametest_attached_session_no_server— a copy-paste leftover that matches neither function name.)Reproduction
Result:
Same pattern reproduces for the other two:
Root cause
Serverwith hardcodedsocket_namestrings./tmp/tmux-<uid>/<socket_name>persists indefinitely.unlink(2)its socket on non-graceful exit, so leftover socket files accumulate in/tmp/tmux-<uid>/.Server(socket_name=...).is_alive()correctly reportsTruebecause the daemon really is alive — the test's assumption that the name is unused is the wrong premise.In my environment two such daemons had been listening since 3 days prior, with PIDs 984553 and 984791, on
/tmp/tmux-1000/test_attached_session_no_serverand/tmp/tmux-1000/test_no_server_is_aliverespectively.lsof -U | grep tmuxconfirmed live LISTEN sockets on both paths.Proposed fix
Use the existing
serverfixture (src/libtmux/pytest_plugin.py:144-182) — it already produces aServerwith a unique random socket name (libtmux_test{N}) and registers a_reap_test_serverfinalizer that kills any daemon and unlinks the socket file. A freshly-createdServerwith a unique name has no daemon running on it, so it is the "dead server" these tests need.This eliminates every hardcoded socket name, removes the typo coupling, and inherits cleanup automatically.
Workaround for affected users
Environment