Skip to content

Releases: tmux-python/libtmux

v0.57.0

18 May 02:53

Choose a tag to compare

libtmux 0.57.0 broadens tmux support around attached clients, tmux-native filtering, and format-token fields. Client gives callers a typed object for attached terminals, search_*() methods let tmux return only matching sessions, windows, and panes, and more tmux format tokens are exposed as typed attributes. LibTmuxException now records which tmux subcommand failed, making command errors easier to handle downstream.

Full release notes: https://libtmux.git-pull.com/history.html#libtmux-0-57-0-2026-05-17

Breaking changes

LibTmuxException string form gains a subcommand prefix

When LibTmuxException is raised from a libtmux method, str(exc) now begins with the originating tmux subcommand name followed by ": ". An error from Session.last_window() used to render as "can't find window" and now renders as "last-window: can't find window".

exc.args[0] still carries the original tmux error text, and the new LibTmuxException.subcommand attribute exposes the tmux subcommand name as a separate field.

The error-payload type also changed: exc.args[0] is now str, joined with "\n" when tmux emitted multiple stderr lines. Previously it was list[str]. Code that pattern-matches on str(exc) exactly, anchors a regex with ^ against the old shape, or indexes exc.args[0] element-by-element will no longer match. Substring matches ("can't find" in str(exc)) and unanchored re.search patterns continue to work unchanged.

See the migration guide for the upgrade pattern.

Server.sessions, Server.clients, Server.search_sessions raise on tmux errors

Previously, a tmux command failure under these accessors could return an empty QueryList indistinguishable from "no sessions / no clients attached" or "filter matched nothing". They now let LibTmuxException propagate for real tmux failures.

Genuine empty results — a server with no attached clients, or a filter that matched zero sessions — still return an empty QueryList. A missing or not-yet-started tmux server is also still treated as an empty result, preserving the historic contract that a fresh Server can be safely introspected before its daemon is up. Other tmux errors, such as socket permission failures or unsupported flags, now surface.

What's new

  • Client object and Server.clients accessor. New typed dataclass for tmux's attached-client model. client_name is the stable identifier; attached_session / attached_window / attached_pane re-read list-clients before resolving. attached_pane follows the attached session's current window — that can differ from the per-client active pane set by select-pane -P.
  • Server.display_message and Window.display_message. Server reads like #{version} and #{socket_path} work without a pane handle; window reads (#{window_zoomed_flag}, #{window_active_clients_list}) auto-bind to the window's id. All three display_message wrappers (including the existing Pane.display_message) surface tmux stderr via warnings.warn rather than dropping it silently.
  • tmux-native filtering with search_*(). Server.search_panes / search_windows / search_sessions plus the Session and Window analogues take a filter= kwarg routed to tmux's -f flag. tmux evaluates the expression and returns only matching objects. Caveat: tmux silently expands a malformed expression to empty, which it treats as false — a typo looks identical to "no matches".
  • Pane.send_keys(cmd=None, …) flag-only invocation. Pass cmd=None together with reset=True or repeat=N to invoke tmux's flag-only send-keys -R / send-keys -N <n> form without any trailing key argument.
  • Server.list_buffers(format_string=, filter=). Project a chosen -F template or push a buffer-name match expression through tmux's format engine — same bad-filter caveat as the search_* methods.
  • Server.run_shell(cwd=, show_stderr=). New kwargs map to tmux's -c (3.4+) and -E (3.6+) flags. Older tmux warns and ignores the kwarg instead of erroring.
  • Pane.capture_pane(pending=True). Return bytes tmux has read from the pane but not yet committed to the terminal — useful for diagnosing programs whose output stalls mid-sequence.
  • More format-token fields on tmux objects. Pane / Window / Session / Client expose more typed attributes for pane state (pane_dead, pane_in_mode, pane_marked, pane_synchronized, pane_path, pane_pipe), window state (window_zoomed_flag, window_silence_flag, window_flags), session state (session_marked), and client state (client_session, client_readonly, client_termtype). Tokens the running tmux does not support stay None instead of making the listing fail.

Fixes

  • Pane.reset() now clears pane scrollback. In 0.56.0 the history clear silently no-op'd, leaving the scrollback intact (#650).

Documentation

  • New API page: libtmux.client.
  • New Format-Token Fields topic explains why some fields describe an active child object — for example, session.pane_id reflects the active pane in the session's current window.

What's Changed

  • CHANGES(docs): rewrite release history for May 2026 by @tony in #669
  • Increase tmux coverage: Client, typed fields, Native filtering by @tony in #672

Full Changelog: v0.56.0...v0.57.0
libtmux on  master [?] ❯ cat release.md
libtmux 0.57.0 broadens tmux support around attached clients, tmux-native filtering, and format-token fields. Client gives callers a typed object for attached terminals, search_*() methods let tmux return only matching sessions, windows, and panes, and more tmux format tokens are exposed as typed attributes. LibTmuxException now records which tmux subcommand failed, making command errors easier to handle downstream.

Full release notes: https://libtmux.git-pull.com/history/#libtmux-0-57-0-2026-05-17

Breaking changes

LibTmuxException string form gains a subcommand prefix

When LibTmuxException is raised from a libtmux method, str(exc) now begins with the originating tmux subcommand name followed by ": ". An error from Session.last_window() used to render as "can't find window" and now renders as "last-window: can't find window".

exc.args[0] still carries the original tmux error text, and the new LibTmuxException.subcommand attribute exposes the tmux subcommand name as a separate field. raise_if_stderr() is the shared helper that populates both.

The error-payload type also changed: exc.args[0] is now str, joined with "\n" when tmux emitted multiple stderr lines. Previously it was list[str]. Code that pattern-matches on str(exc) exactly, anchors a regex with ^ against the old shape, or indexes exc.args[0] element-by-element will no longer match. Substring matches ("can't find" in str(exc)) and unanchored re.search patterns continue to work unchanged.

See the migration guide for the upgrade pattern.

Server.sessions, Server.clients, Server.search_sessions raise on tmux errors

Previously, a tmux command failure under Server.sessions, Server.clients, or Server.search_sessions() could return an empty QueryList indistinguishable from "no sessions / no clients attached" or "filter matched nothing". They now let LibTmuxException propagate for real tmux failures.

Genuine empty results — a server with no attached clients, or a filter that matched zero sessions — still return an empty QueryList. A missing or not-yet-started tmux server is also still treated as an empty result, preserving the historic contract that a fresh Server can be safely introspected before its daemon is up. Other tmux errors, such as socket permission failures or unsupported flags, now surface.

What's new

Read more

v0.56.0

10 May 13:48

Choose a tag to compare

The tmux command parity release. ~50 new public methods land across Server, Pane, Window, and Session, plus expanded flag coverage on existing wrappers, plus a control_mode test fixture that lets commands requiring an attached client run under CI without a TTY. Net result: callers no longer need to drop down to Server.cmd(...) for the commands tmux ships out of the box.

No breaking changes — public API is purely additive.

Highlights

Interactive tmux commands now scriptable

New wrappers for commands that require an attached client:

The three detach-client wrappers each map to one tmux flag group exactly, with a single subprocess call:

Wrapper tmux invocation Scope
Session.detach_client() tmux detach-client -s <session_id> every client in this session
Server.detach_client(target_client=...) tmux detach-client [-t <client>] server-wide single-client lookup
Server.detach_all_clients(target_client=...) tmux detach-client -a [-t <keep>] server-wide, optionally preserving one client

tmux buffer I/O suite

Round-trip pane content through named tmux buffers — useful for clipboard interop and inter-process data hand-off:

Key bindings, shell execution, and client management

Window and pane manipulation parity

Filled-in flag coverage on existing methods

Most pre-existing wrappers now expose the remaining tmux flags:

  • Pane.send_keysliteral, hex_keys, key_name, expand_formats, target_client, repeat, reset, copy_mode_cmd
  • Pane.splitpercentage=
  • Pane.capture_panealternate_screen, quiet, mode_screen, to_buffer (writes capture into a tmux buffer instead of returning it)
  • <a href="https://libt...
Read more

v0.55.1 - improved docs, pytest teardown

19 Apr 18:26

Choose a tag to compare

A point release focused on a pytest-plugin cleanup fix, a new Sphinx extension for documenting pytest fixtures, and a docs-stack migration to gp-sphinx.

Highlights

Fix: pytest_plugin leaks tmux socket files on teardown (#661, fixes #660)

The server and TestServer fixtures now unlink(2) the tmux socket from /tmp/tmux-<uid>/ during teardown, in addition to calling server.kill(). tmux does not reliably remove its own socket on non-graceful exit, so /tmp/tmux-<uid>/ would accumulate stale libtmux_test* entries across runs — 10k+ observed on long-lived dev machines.

A new internal _reap_test_server helper centralizes the kill + unlink flow and suppresses cleanup-time errors, so a finalizer failure can no longer mask the real test failure.

If you use libtmux's pytest plugin in CI or locally, upgrade to stop the leak.

New: Sphinx extension for pytest fixture documentation (#656)

A new Sphinx extension (docs/_ext/sphinx_pytest_fixtures.py) renders pytest fixtures as first-class API documentation with scope/kind/factory badges, cross-referenced dependencies, and auto-generated usage snippets.

  • .. autofixture:: — autodoc-style documenter for individual fixtures
  • .. autofixtures:: — bulk discovery and rendering from a module
  • .. autofixture-index:: — auto-generated index table with linked return types (via intersphinx) and parsed RST descriptions
  • :fixture: cross-reference role with short-name resolution
  • Scope, kind, and autouse badges with touch-accessible tooltips
  • Responsive layout (mobile metadata stacking, badge font scaling, scroll wrappers)
  • WCAG AA contrast compliance in both light and dark mode
  • 109 tests (unit + integration)

Also fixes an inaccurate session_params fixture docstring surfaced by the new extension.

Documentation

  • Docs restructured to the Library Skeleton pattern (#652)
  • Self-hosted fonts, eliminated layout shift, added SPA-style navigation (#643)
  • Standardized shell code-block formatting across all docs (#644)
  • Migrated the docs stack to gp-sphinx workspace packages (#657)
  • Visual improvements to API docs via gp-sphinx (#658)
  • Bumped gp-sphinx to v0.0.1a8 (#659); subsequent bump to v0.0.1a9 on master

Development

  • Added types-docutils to dev dependencies for mypy type checking (#656)

What's Changed

  • docs: self-host fonts, eliminate layout shift, add SPA navigation by @tony in #643
  • docs(style[shell]): Standardize shell code blocks by @tony in #644
  • docs(redesign): restructure documentation to Library Skeleton pattern by @tony in #652
  • feat(_ext[sphinx_pytest_fixtures]): Sphinx extension for pytest fixture documentation by @tony in #656
  • docs: Migrate to gp-sphinx workspace packages by @tony in #657
  • docs(feat[api-style]): Visual improvements to API docs via gp-sphinx by @tony in #658
  • chore(docs): adopt gp-sphinx v0.0.1a8 by @tony in #659
  • fix(pytest_plugin): unlink socket file on fixture teardown by @tony in #661

Full Changelog: v0.55.0...v0.55.1

v0.55.0 - compatibility features, more logging

08 Mar 01:00

Choose a tag to compare

What's Changed

via @tony in #636

Pane.set_title()

New Pane.set_title() method wraps select-pane -T and returns the pane
for method chaining. A Pane.title property aliases pane_title for
convenience:

pane.set_title("my-worker")
pane.pane_title  # 'my-worker'
pane.title       # 'my-worker'

The pane_title format variable is now included in libtmux's pane format
queries (it was previously excluded via an incorrect "removed in 3.1+" comment).

Configurable tmux binary path

Server now accepts a tmux_bin parameter to use an alternative binary
(e.g. wemux, byobu, or a custom build):

server = Server(socket_name="myserver", tmux_bin="/usr/local/bin/tmux-next")

The path is threaded through Server.cmd(), Server.raise_if_dead(),
fetch_objs(), all version-check functions (has_version,
has_gte_version, etc.), and hook scope guards in HooksMixin. Child
objects (Session, Window, Pane) inherit it automatically. Falls back to
shutil.which("tmux") when not set.

Pre-execution command logging

tmux_cmd now emits a structured DEBUG log record with
extra={"tmux_cmd": ...} before invoking the subprocess, using
shlex.join for POSIX-correct quoting. This complements the existing
post-execution stdout log and is a prerequisite for a future dry-run mode.

Bug fix: TmuxCommandNotFound raised for invalid tmux_bin path

Passing a non-existent binary path previously surfaced as a raw
FileNotFoundError from subprocess. Both tmux_cmd and
raise_if_dead now catch FileNotFoundError and raise
TmuxCommandNotFound consistently.

Full Changelog: v0.54.0...v0.55.

v0.54.0 - Revamped logging

07 Mar 16:08

Choose a tag to compare

Highlights

  • Structured lifecycle logging across Server, Session, Window, and Pane with filterable extra context
  • Bug fixes for rename_window(), Server.kill(), new_session(), and kill_window() error handling

What's new

Structured lifecycle logging (#637)

All lifecycle operations (create, kill, rename, split) now emit INFO-level log records with structured extra context. Every log call includes scalar keys for filtering in log aggregators and test assertions via caplog.records:

Key Type Context
tmux_subcommand str tmux subcommand (e.g. new-session)
tmux_target str tmux target specifier
tmux_session str session name
tmux_window str window name or index
tmux_pane str pane identifier

Logging hygiene improvements:

  • NullHandler added to library __init__.py per Python logging best practices
  • DEBUG-level structured logs for tmux_cmd execution with isEnabledFor guards and heavy keys (tmux_stdout, tmux_stderr, tmux_stdout_len, tmux_stderr_len)
  • Lazy formatting: replaced f-string log formatting with %s throughout
  • Diagnostics: replaced traceback.print_stack() with logger.debug(exc_info=True)
  • Options warnings: replaced logger.exception() with logger.warning() and tmux_option_key structured context for recoverable parse failures
  • Cleanup: removed unused logger definitions from modules that don't log

Bug fixes

Window.rename_window() now raises on failure (#637)

Previously rename_window() caught all exceptions and logged them, masking tmux errors. It now propagates the error, consistent with all other command methods.

Server.kill() captures stderr (#637)

Server.kill() previously discarded the tmux return value. It now checks stderr, raises on unexpected errors, and silently returns for expected conditions ("no server running", "error connecting to").

Server.new_session() checks kill-session stderr (#637)

When kill_session=True and the existing session kill fails, new_session() now raises LibTmuxException with the stderr instead of proceeding silently.

Session.kill_window() target formatting fix (#637)

Fixed self.window_name (wrong attribute) to self.session_name when formatting tmux targets for integer target_window values. Also widened the type signature from str | None to str | int | None to match the existing isinstance(target_window, int) branch.


Installation

pip:

$ pip install libtmux==0.54.0

uv:

$ uv add libtmux==0.54.0

What's Changed

  • Add structured logging with lifecycle events and test coverage by @tony in #637

Full Changelog: v0.53.1...v0.54.0

v0.53.1 - Bug fix for new sessions

19 Feb 00:48

Choose a tag to compare

What's Changed

Bug fixes

  • Fix race condition in new_session() by avoiding list-sessions query by @neubig in #625

Development

  • build: Migrate from Makefile to justfile by @tony in #617

New Contributors

Full Changelog: v0.53.0...v0.53.1

v0.53.0 - Bug fixes

14 Dec 12:14

Choose a tag to compare

A focused maintenance release that fixes a critical bug in Session.attach() that caused tracebacks when users killed sessions while attached via tmuxp load.

Highlights

  • Fixed: Session.attach() no longer raises TmuxObjectDoesNotExist when a user kills the session during attachment
  • Breaking: Session.attach() no longer calls refresh() after returning (semantically incorrect for interactive commands)

Bug Fixes

Session.attach() no longer fails if session killed during attachment (#616)

Fixed an issue where Session.attach() would raise TmuxObjectDoesNotExist when a user:

  1. Attaches to a tmux session via tmuxp load
  2. Works in the session
  3. Kills the session (e.g., closes all windows) before detaching
  4. Detaches from tmux

User Experience (Before Fix)

After running tmuxp load, users would see this traceback printed to their terminal after detaching:

Traceback (most recent call last):
  File "~/.local/bin/tmuxp", line 7, in <module>
    sys.exit(cli.cli())
  ...
  File ".../libtmux/session.py", line 332, in attach
    self.refresh()
  File ".../libtmux/neo.py", line 167, in _refresh
    obj = fetch_obj(...)
  File ".../libtmux/neo.py", line 242, in fetch_obj
    raise exc.TmuxObjectDoesNotExist(...)
libtmux.exc.TmuxObjectDoesNotExist: Could not find object

Root Cause

Session.attach() called self.refresh() after the attach-session command returned. Since attach-session is a blocking interactive command, the session state can change arbitrarily during attachment—including being killed entirely.

Technical Details

The refresh() call was semantically incorrect for interactive commands:

  • attach-session blocks until user detaches
  • Session state can change during attachment (user may kill session, rename it, etc.)
  • Refreshing the object after such a command makes no sense—the state could be anything

The fix: Remove the self.refresh() call from Session.attach() (2 lines removed).


Breaking Changes

Session.attach() no longer calls refresh() (#616)

Session.attach() previously called Session.refresh() after the attach-session command returned. This was semantically incorrect since attach-session is a blocking interactive command where session state can change arbitrarily during attachment.

This was never strictly defined behavior as libtmux abstracts tmux internals away. Code that relied on the session object being refreshed after attach() should explicitly call session.refresh() if needed.

Migration

# If you relied on the implicit refresh (unlikely):
session.attach()
session.refresh()  # Now explicit if you need it

Timeline

Date Event
Feb 2024 Session.attach() added with refresh() call (9a5147a)
Nov 2025 tmuxp switched from attach_session() to attach() (fdafdd2b)
Dec 2025 Users started experiencing the bug
Dec 2025 v0.53.0 released with fix

Installation

pip:

pip install libtmux==0.53.0

uv:

uv add libtmux==0.53.0

pipx (for tmuxp users):

pipx upgrade tmuxp

What's Changed

  • fix(Session.attach()): Remove refresh() call that fails after session killed by @tony in #616

Full Changelog: v0.52.1...v0.53.0

v0.52.1 - Trusted Publisher for PyPI builds

07 Dec 21:42

Choose a tag to compare

Development

  • ci(release): Migrate to PyPI Trusted Publisher by @tony in #615

Full Changelog: v0.52.0...v0.52.1

v0.52.0 - `send_keys()` updates

07 Dec 20:49

Choose a tag to compare

libtmux 0.52.0

capture_pane() enhancements

The Pane.capture_pane() method now supports 5 new parameters exposing additional tmux capture-pane flags:

Parameter tmux Flag Description
escape_sequences -e Include ANSI escape sequences (colors, attributes)
escape_non_printable -C Escape non-printable chars as octal \xxx
join_wrapped -J Join wrapped lines back together
preserve_trailing -N Preserve trailing spaces at line ends
trim_trailing -T Trim trailing empty positions (tmux 3.4+)

Examples

Capturing colored output:

# Capture with ANSI escape sequences preserved
pane.send_keys('printf "\\033[31mRED\\033[0m"', enter=True)
output = pane.capture_pane(escape_sequences=True)
# Output contains: '\x1b[31mRED\x1b[0m'

Joining wrapped lines:

# Long lines that wrap are joined back together
output = pane.capture_pane(join_wrapped=True)

Version compatibility

The trim_trailing parameter requires tmux 3.4+. If used with an older version, a warning is issued and the flag is ignored. All other parameters work with libtmux's minimum supported version (tmux 3.2a).

What's Changed

Full Changelog: v0.51.0...v0.52.0

v0.51.0 (Breaking API deprecations)

06 Dec 21:16

Choose a tag to compare

Breaking Changes

Deprecate legacy APIs

Legacy API methods (deprecated in v0.16–v0.33) now raise DeprecatedError (hard error) instead of emitting DeprecationWarning.

See the migration guide for full context and examples.

  • Deprecate legacy APIs (raise DeprecatedError) by @tony in #611

Method Renamings

Deprecated Replacement Class Deprecated Since
kill_server() kill() Server 0.30.0
attach_session() attach() Session 0.30.0
kill_session() kill() Session 0.30.0
select_window() select() Window 0.30.0
kill_window() kill() Window 0.30.0
split_window() split() Window 0.33.0
select_pane() select() Pane 0.30.0
resize_pane() resize() Pane 0.28.0
split_window() split() Pane 0.33.0

Property Renamings

Deprecated Replacement Class Deprecated Since
attached_window active_window Session 0.31.0
attached_pane active_pane Session 0.31.0
attached_pane active_pane Window 0.31.0

Query/Filter API Changes

Deprecated Replacement Class Deprecated Since
list_sessions() / _list_sessions() sessions property Server 0.17.0
list_windows() / _list_windows() windows property Session 0.17.0
list_panes() / _list_panes() panes property Window 0.17.0
where({...}) .filter(**kwargs) on sessions/windows/panes All 0.17.0
find_where({...}) .get(default=None, **kwargs) on sessions/windows/panes All 0.17.0
get_by_id(id) .get(session_id/window_id/pane_id=..., default=None) All 0.16.0
children property sessions/windows/panes All 0.17.0

Attribute Access Changes

Deprecated Replacement Deprecated Since
obj['key'] obj.key 0.17.0
obj.get('key') obj.key 0.17.0
obj.get('key', None) getattr(obj, 'key', None) 0.17.0

Still Soft Deprecations (DeprecationWarning)

The following deprecations from v0.50.0 continue to emit DeprecationWarning only:

Deprecated Replacement Class
set_window_option() set_option() Window
show_window_option() show_option() Window
show_window_options() show_options() Window
g parameter global_ parameter Options & hooks methods

Migration Example

Before (deprecated, now raises DeprecatedError):

# Old method names
server.kill_server()
session.attach_session()
window.split_window()
pane.resize_pane()

# Old query API
server.list_sessions()
session.find_where({'window_name': 'main'})

# Old dict-style access
window['window_name']

After:

# New method names
server.kill()
session.attach()
window.split()
pane.resize()

# New query API
server.sessions
session.windows.get(window_name='main', default=None)

# New attribute access
window.window_name

Links

Full Changelog: v0.50.1...v0.51.0