`Server.__exit__` kills any live server, including one it did not start

Open
#724 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
python
Domain
api, cli

Research direction

Start in src/libtmux/server.py at init and exit around lines 267-285, then read docs/topics/context_managers.md and compare the sibling context managers. Add a regression test showing that a pre-existing server survives a with block, document the shared-server hazard, and verify the chosen lifecycle behavior preserves the intended default.

Written by the indexing model from the issue text.

Description

Filed against tmux-python/libtmux v0.62.0. Found while writing tested documentation examples that isolate tmux under a temporary TMUX_TMPDIR.

What happens

with Server(...) as server: kills the server on exit whenever it is alive, unconditionally. There is no kill_on_exit=False, no "only kill what I started", and no way to use the context manager purely for scoping.

        if self.is_alive():
            self.kill()

A Server is a handle, not a connection, and the same handle addresses a server whether or not this process started it. So the context manager cannot tell "the server I just booted" from "the server the user has been working in all day", and it destroys both.

The sharpest form: with Server() as server: — no arguments, so the default socket — kills the reader's running tmux and every session in it. That is a plausible thing to write after reading the class docstring, and the failure is silent and total.

Recreation

import os, tempfile
os.environ["TMUX_TMPDIR"] = tempfile.mkdtemp(prefix="repro-")
from libtmux.server import Server

Server(socket_name="preexisting").new_session(session_name="important-work")
print("before:", [s.session_name for s in Server(socket_name="preexisting").sessions])

with Server(socket_name="preexisting") as server:
    print("inside:", server.is_alive())

print("after :", Server(socket_name="preexisting").is_alive(), "<- the session is gone")

Observed on libtmux v0.62.0 / tmux 3.7b:

before: ['important-work']
inside: True
after : False <- the session is gone

The with block did nothing except enter and exit, and a session it never created is gone. Drop the TMUX_TMPDIR line and the same script destroys the real default server.

Note on the sibling context managers

Session, Window and Pane have the same shape — __exit__ kills the object if it still exists — but the blast radius there is bounded by what the object is, and killing a session you were handed is at least proportionate. A server is different in kind: it is shared, process-wide, and typically not yours.

Documented in docs/topics/context_managers.md, which describes the kill-on-exit behaviour but not the shared-server hazard.

What a fix needs

  1. Add kill_on_exit: bool = True to Server.__init__, honoured by __exit__. Keeps the current default, gives callers an out. Cheapest change, no behaviour break.
  2. Better, if a break is acceptable in a pre-1.0 minor: only kill a server this handle started. Server already knows whether it booted the daemon — new_session is the call that starts one — so record that and have __exit__ respect it. This makes the destructive path opt-in by construction rather than by remembering a keyword.
  3. Either way: document the hazard on Server.__enter__/__exit__ and in docs/topics/context_managers.md, in the same terms as the recreation above, and add a regression test that a pre-existing server survives a with block.

Option 2 with a kill_on_exit=True escape hatch would cover both audiences. If neither is wanted, the minimum is a loud warning in the docstring — the current one says "killing the server if it exists" without saying whose.

Dominant language
Python
Stars
1.2k
Forks
127
Avg merge
2h 13m
Merged PRs (30d)
1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tmux-python/libtmux

All issues in tmux-python/libtmux

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.