From 5952b9e01ed704ad17af210583062b2138512d8d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 19 Apr 2026 13:04:46 -0500 Subject: [PATCH 1/2] test(conftest): reap leaked libtmux_test* sockets after session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream libtmux's pytest plugin (tmux-python/libtmux#660) creates per-test tmux servers on `libtmux_test` sockets but does not reliably kill the daemons or unlink the socket files on teardown. Over repeated runs, `/tmp/tmux-/` accumulates stale socket entries indefinitely (observed: 10k+ on a dev machine after normal activity, surfaced by the new `list_servers` tool). Add a session-scoped autouse fixture that, after every pytest run, globs `/tmp/tmux-/libtmux_test*`, kills live daemons, and unlinks stale socket files. Prefix match keeps the reaper away from the developer's real `default` socket. Errors are suppressed — the cleanup is advisory and any failure is a race with a concurrent xdist worker or a vanished socket, neither actionable. Verified locally: `ls /tmp/tmux-$(id -u) | grep -c libtmux_test` dropped from 10693 to 0 after a single `uv run pytest`. Closes #20 --- CHANGES | 10 ++++++++++ tests/conftest.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 51f05bd..78a1b9b 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,16 @@ _Notes on upcoming releases will be added here_ +### Testing + +- Added a session-scoped autouse fixture in `tests/conftest.py` that + reaps leaked `libtmux_test*` tmux daemons and socket files under + `/tmp/tmux-/` after every test run. Works around upstream + [tmux-python/libtmux#660](https://github.com/tmux-python/libtmux/issues/660), + whose pytest plugin does not reliably kill servers or unlink socket + files during teardown. Idempotent and safe under `pytest-xdist` + (#20). + ## libtmux-mcp 0.1.0a2 (2026-04-19) _FastMCP alignment: new tools, prompts, and middleware (#15)_ diff --git a/tests/conftest.py b/tests/conftest.py index 3f7dc93..2da7c05 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,19 +2,67 @@ from __future__ import annotations +import contextlib +import os +import pathlib import typing as t import pytest +from libtmux.server import Server from libtmux_mcp._utils import _server_cache if t.TYPE_CHECKING: from libtmux.pane import Pane - from libtmux.server import Server from libtmux.session import Session from libtmux.window import Window +@pytest.fixture(scope="session", autouse=True) +def _reap_leaked_libtmux_test_sockets() -> t.Generator[None, None, None]: + """Reap leaked ``libtmux_test*`` daemons and socket files post-suite. + + libtmux's pytest plugin creates per-test tmux servers on + ``libtmux_test`` sockets but does not reliably kill the daemons + or ``unlink`` the socket files on teardown — see + `tmux-python/libtmux#660 `_. + Without this finalizer ``/tmp/tmux-/`` accumulates hundreds of + stale socket entries across test runs (10k+ on long-lived dev + machines per the #20 report). + + Scope is ``session``: runs after every ``pytest`` invocation. Prefix + match on ``libtmux_test`` only — matches the literal prefix set by + libtmux's ``pytest_plugin.py`` and never touches the developer's + real ``default`` socket or any non-test socket. Safe under ``xdist``: + each worker is its own pytest session and the socket operations + (``kill_server`` / ``unlink``) are idempotent. + """ + yield + + # ``geteuid`` is Unix-only; the tmux server socket directory only + # exists on POSIX. Skip on platforms without it rather than erroring. + if not hasattr(os, "geteuid"): + return + + tmpdir = pathlib.Path(f"/tmp/tmux-{os.geteuid()}") + if not tmpdir.is_dir(): + return + + for socket_path in tmpdir.glob("libtmux_test*"): + # Defensive cleanup: if the server is still alive, kill it; then + # unlink the socket file whether or not kill succeeded (tmux + # sometimes leaves the file on disk after the daemon exits). + # Any step may fail because the socket has already vanished, + # permissions changed, or a concurrent run raced us — none of + # that is actionable here, so swallow the error and move on. + with contextlib.suppress(Exception): + server = Server(socket_name=socket_path.name) + if server.is_alive(): + server.kill() + with contextlib.suppress(OSError): + socket_path.unlink(missing_ok=True) + + @pytest.fixture(autouse=True) def _clear_server_cache() -> t.Generator[None, None, None]: """Clear the MCP server cache between tests.""" From 5e2f949cfd55776b769b85d1fe8a87b9efb8dca5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 19 Apr 2026 13:20:14 -0500 Subject: [PATCH 2/2] docs(CHANGES): point to fix PR #661 instead of issue #660 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 78a1b9b..a858074 100644 --- a/CHANGES +++ b/CHANGES @@ -11,7 +11,7 @@ _Notes on upcoming releases will be added here_ - Added a session-scoped autouse fixture in `tests/conftest.py` that reaps leaked `libtmux_test*` tmux daemons and socket files under `/tmp/tmux-/` after every test run. Works around upstream - [tmux-python/libtmux#660](https://github.com/tmux-python/libtmux/issues/660), + [tmux-python/libtmux#661](https://github.com/tmux-python/libtmux/pull/661), whose pytest plugin does not reliably kill servers or unlink socket files during teardown. Idempotent and safe under `pytest-xdist` (#20).