Releases: tmux-python/libtmux
v0.55.0 - compatibility features, more logging
What's Changed
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
Highlights
- Structured lifecycle logging across Server, Session, Window, and Pane with filterable
extracontext - Bug fixes for
rename_window(),Server.kill(),new_session(), andkill_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__.pyper Python logging best practices - DEBUG-level structured logs for
tmux_cmdexecution withisEnabledForguards and heavy keys (tmux_stdout,tmux_stderr,tmux_stdout_len,tmux_stderr_len) - Lazy formatting: replaced f-string log formatting with
%sthroughout - Diagnostics: replaced
traceback.print_stack()withlogger.debug(exc_info=True) - Options warnings: replaced
logger.exception()withlogger.warning()andtmux_option_keystructured 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.0uv:
$ uv add libtmux==0.54.0What's Changed
Full Changelog: v0.53.1...v0.54.0
v0.53.1 - Bug fix for new sessions
v0.53.0 - Bug fixes
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 raisesTmuxObjectDoesNotExistwhen a user kills the session during attachment - Breaking:
Session.attach()no longer callsrefresh()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:
- Attaches to a tmux session via
tmuxp load - Works in the session
- Kills the session (e.g., closes all windows) before detaching
- 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-sessionblocks 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 itTimeline
| 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.0uv:
uv add libtmux==0.53.0pipx (for tmuxp users):
pipx upgrade tmuxpWhat's Changed
Full Changelog: v0.52.1...v0.53.0
v0.52.1 - Trusted Publisher for PyPI builds
Development
Full Changelog: v0.52.0...v0.52.1
v0.52.0 - `send_keys()` updates
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)
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.
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_nameLinks
Full Changelog: v0.50.1...v0.51.0
v0.50.1 - Maintenance release
v0.50.0 - options and hook management
libtmux 0.50 brings a major enhancement to option and hook management with a unified, typed API for managing tmux options and hooks across all object types.
Highlights
- Unified Options API: New
show_option(),show_options(),set_option(), andunset_option()methods available on Server, Session, Window, and Pane - Hook Management: Full programmatic control over tmux hooks with support for indexed hook arrays and bulk operations
- SparseArray: New internal data structure for handling tmux's sparse indexed arrays (e.g.,
command-alias[0],command-alias[99]) - tmux 3.2+ baseline: Removed support for tmux versions below 3.2a, enabling cleaner code and full hook/option feature support
Unified Options API
All tmux objects now share a consistent options interface:
import libtmux
server = libtmux.Server()
session = server.sessions[0]
window = session.windows[0]
# Get all options as a structured dict
session.show_options()
# {'activity-action': 'other', 'base-index': 0, ...}
# Get a single option value
session.show_option('base-index')
# 0
# Set an option
window.set_option('automatic-rename', True)
# Unset an option (revert to default)
window.unset_option('automatic-rename')Hook Management
Programmatic control over tmux hooks:
session = server.sessions[0]
# Set a hook
session.set_hook('session-renamed', 'display-message "Renamed!"')
# Get hook value (returns SparseArray for indexed hooks)
session.show_hook('session-renamed')
# {0: 'display-message "Renamed!"'}
# Get all hooks
session.show_hooks()
# Remove a hook
session.unset_hook('session-renamed')
# Bulk operations for indexed hooks
session.set_hooks('session-renamed', {
0: 'display-message "Hook 0"',
1: 'display-message "Hook 1"',
5: 'run-shell "echo hook 5"',
})Breaking Changes
Deprecated Window methods
The following methods are deprecated and will be removed in a future release:
| Deprecated | Replacement |
|---|---|
Window.set_window_option() |
Window.set_option() |
Window.show_window_option() |
Window.show_option() |
Window.show_window_options() |
Window.show_options() |
Deprecated g parameter
The g parameter for global options is deprecated in favor of global_:
# Before (deprecated)
session.show_option('status', g=True)
# After (0.50.0+)
session.show_option('status', global_=True)New Constants
OptionScopeenum:Server,Session,Window,PaneOPTION_SCOPE_FLAG_MAP: Maps scope to tmux flags (-s,-w,-p)HOOK_SCOPE_FLAG_MAP: Maps scope to hook flags
Documentation
- New topic guide: Options and Hooks
- New topic guides: Automation patterns, Workspace setup, Pane interaction, QueryList filtering
- Refreshed README with hero section, quickstart, and more examples
tmux Version Compatibility
| Feature | Minimum tmux |
|---|---|
| All options/hooks features | 3.2+ |
Window/Pane hook scopes (-w, -p) |
3.2+ |
client-active, window-resized hooks |
3.3+ |
pane-title-changed hook |
3.5+ |
What's Changed
Full Changelog: v0.49.0...v0.50.0