Skip to content

Commit 064de55

Browse files
committed
Server(fix[errors]): Clarify public wording and socket failures
why: PR #672 should describe shipped user behavior without branch-internal jargon, and real tmux connection failures must not be hidden as empty listings. Behavior: - Treat only missing/not-yet-started tmux sockets as empty listing results. Real connection failures (permission denied, ECONNRESET on a live socket, malformed daemon response) now propagate as LibTmuxException instead of being swallowed as an empty QueryList. - Regression coverage for the missing-socket versus permission-denied listing paths. Prose: - Replace "C-side filter" with "tmux-native filtering" in release notes, MIGRATION, topic docs, and API docstrings. - Replace "format-token hydration" with "format-token fields" in the same locations.
1 parent a737b7e commit 064de55

15 files changed

Lines changed: 297 additions & 341 deletions

File tree

CHANGES

Lines changed: 58 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,32 @@ $ uvx --from 'libtmux' --prerelease allow python
4545
_Notes on the upcoming release will go here._
4646
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->
4747

48-
libtmux 0.57.0 broadens tmux support. It introduces
49-
{class}`~libtmux.Client` as a first-class object, threads tmux's
50-
native ``-f`` filter through the typed listing methods so callers can
51-
push predicates into the tmux server, and adds typed access to many
52-
more format tokens — all scope- and version-gated so they're safe on
53-
every supported tmux version. Subcommand context now flows through
54-
{exc}`~libtmux.exc.LibTmuxException`, making it easier for downstream
55-
tools to dispatch on which tmux command produced an error.
48+
libtmux 0.57.0 broadens tmux support around attached clients, tmux-native
49+
filtering, and format-token fields. {class}`~libtmux.Client` gives callers a
50+
typed object for attached terminals, `search_*()` methods let tmux return only
51+
matching sessions, windows, and panes, and more tmux format tokens are exposed
52+
as typed attributes. {exc}`~libtmux.exc.LibTmuxException` now records which
53+
tmux subcommand failed, making command errors easier to handle downstream.
5654

5755
### Breaking changes
5856

5957
#### `LibTmuxException` string form gains a subcommand prefix (#672)
6058

61-
When {exc}`~libtmux.exc.LibTmuxException` is raised from one of the
62-
typed command wrappers, ``str(exc)`` now begins with the originating
63-
tmux subcommand name followed by ``": "``. For example, an error from
59+
When {exc}`~libtmux.exc.LibTmuxException` is raised from a libtmux method,
60+
``str(exc)`` now begins with the originating tmux subcommand name followed
61+
by ``": "``. For example, an error from
6462
{meth}`~libtmux.Session.last_window` used to render as ``"can't find
6563
window"`` and now renders as ``"last-window: can't find window"``.
6664

67-
``exc.args[0]`` still carries the raw tmux output, and the new
65+
``exc.args[0]`` still carries the original tmux error text, and the new
6866
{attr}`~libtmux.exc.LibTmuxException.subcommand` attribute exposes
6967
the tmux subcommand name as a separate field.
7068
{func}`~libtmux.common.raise_if_stderr` is the shared helper that
7169
populates both.
7270

73-
The wrapped-stderr *type* changed: ``exc.args[0]`` is now ``str``,
71+
The error-payload *type* changed: ``exc.args[0]`` is now ``str``,
7472
joined with ``"\n"`` when tmux emitted multiple stderr lines.
75-
Previously it was ``list[str]`` — the raw ``proc.stderr`` was
76-
passed through to :class:`Exception` directly.
73+
Previously it was ``list[str]``.
7774

7875
This is a serialization-format change: code that pattern-matches on
7976
``str(exc)`` exactly, anchors a regex with ``^`` against the old
@@ -95,7 +92,7 @@ except LibTmuxException as exc:
9592
if exc.subcommand == "last-window":
9693
...
9794

98-
# Or — match against the raw stderr without the prefix
95+
# Or — match against the original tmux error text without the prefix
9996
try:
10097
session.last_window()
10198
except LibTmuxException as exc:
@@ -110,20 +107,19 @@ Substring matches (``"can't find" in str(exc)``) and unanchored
110107

111108
Previously, a tmux command failure under
112109
{attr}`~libtmux.Server.sessions`, {attr}`~libtmux.Server.clients`, or
113-
{meth}`~libtmux.Server.search_sessions` was swallowed by a bare
114-
``except Exception: pass``, returning an empty
110+
{meth}`~libtmux.Server.search_sessions` could return an empty
115111
{class}`~libtmux._internal.query_list.QueryList` indistinguishable
116112
from "no sessions / no clients attached" or "filter matched nothing".
117-
The wrappers now let {exc}`~libtmux.exc.LibTmuxException` propagate
118-
via {func}`~libtmux.common.raise_if_stderr`.
113+
Those accessors now let {exc}`~libtmux.exc.LibTmuxException` propagate
114+
for real tmux failures.
119115

120116
Genuine empty results — a server with no attached clients, or a
121117
filter that matched zero sessions — still return an empty
122-
``QueryList``. ``"no server running"`` is also still treated as
123-
an empty result, preserving the historic contract that a fresh
124-
{class}`~libtmux.Server` can be safely introspected before its
125-
daemon is up. Only real tmux errors (subprocess crash, malformed
126-
output, version-incompatible flags) now surface.
118+
``QueryList``. A missing or not-yet-started tmux server is also still
119+
treated as an empty result, preserving the historic contract that a fresh
120+
{class}`~libtmux.Server` can be safely introspected before its daemon is
121+
up. Other tmux errors, such as socket permission failures or unsupported
122+
flags, now surface.
127123

128124
```python
129125
# Before — silent on tmux failure
@@ -144,17 +140,15 @@ else:
144140

145141
#### `Client` object and `Server.clients` accessor (#672)
146142

147-
New {class}`~libtmux.Client` dataclass and
148-
{attr}`~libtmux.Server.clients` property bring typed-ORM ergonomics
149-
to tmux's attached-client model. Reads like ``client.client_readonly``
150-
and ``client.client_session`` work directly on the client instead of
151-
forcing callers down to {meth}`~libtmux.Server.cmd`.
143+
New {class}`~libtmux.Client` and {attr}`~libtmux.Server.clients` support tmux's
144+
attached-client model directly. Reads like ``client.client_readonly`` and
145+
``client.client_session`` work on the client object without dropping down to
146+
{meth}`~libtmux.Server.cmd`.
152147

153-
Note that ``client.session_id`` / ``client.window_id`` /
154-
``client.pane_id`` reflect the client's currently attached view at
155-
hydration time — {meth}`~libtmux.Client.refresh` re-reads them after
156-
the client switches focus. ``client.client_name`` is the client's
157-
stable identifier.
148+
Note that ``client.session_id`` / ``client.window_id`` / ``client.pane_id``
149+
reflect the client's attached view when the object was read —
150+
{meth}`~libtmux.Client.refresh` re-reads them after the client switches focus.
151+
``client.client_name`` is the client's stable identifier.
158152

159153
For typed access to the live attachment, use
160154
{attr}`~libtmux.Client.attached_session`,
@@ -170,11 +164,10 @@ Direct {meth}`~libtmux.Client.refresh` and
170164
{meth}`~libtmux.Client.from_client_name` calls still surface missing
171165
client lookup errors.
172166

173-
{attr}`~libtmux.Client.attached_pane` is session-scope: it returns
174-
the session's current window's active pane, not the client's
175-
``CLIENT_ACTIVEPANE`` focus. The two diverge once a client has used
176-
``select-pane -P`` to set its own active pane. See
177-
{attr}`~libtmux.Client.attached_pane` for the resolution detail.
167+
{attr}`~libtmux.Client.attached_pane` follows the attached session's current
168+
window. That can differ from the per-client active pane set by
169+
``select-pane -P``; see {attr}`~libtmux.Client.attached_pane` for the exact
170+
behavior.
178171

179172
#### `Server.display_message` and `Window.display_message` (#672)
180173

@@ -190,22 +183,20 @@ rather than dropping it silently. tmux uses stderr for both genuine
190183
errors and informational messages on some versions, so the wrappers
191184
warn rather than raise; callers that want to escalate can wrap the
192185
call in :func:`warnings.catch_warnings` with
193-
``filterwarnings("error")``. See {doc}`MIGRATION` for the escalation
186+
``filterwarnings("error")``. See {doc}`migration` for the escalation
194187
pattern.
195188

196-
#### Native filter on typed listing methods (#672)
189+
#### tmux-native filtering with `search_*()` (#672)
197190

198191
{meth}`~libtmux.Server.search_panes`,
199192
{meth}`~libtmux.Server.search_windows`,
200193
{meth}`~libtmux.Server.search_sessions`, and the Session/Window
201194
analogues take a ``filter=`` kwarg routed to tmux's ``-f`` flag. tmux
202-
evaluates the predicate and drops non-matching objects before any
203-
Python instance is constructed.
195+
evaluates the expression and returns only matching objects.
204196

205-
Caveat: tmux silently expands a malformed predicate to empty, which
206-
the format engine treats as false — a typo looks identical to "no
207-
matches". Verify predicate syntax against the FORMATS section of
208-
``tmux(1)``.
197+
Caveat: tmux silently expands a malformed filter expression to empty, which
198+
it treats as false — a typo looks identical to "no matches". Verify filter
199+
syntax against the FORMATS section of ``tmux(1)``.
209200

210201
#### `Pane.send_keys(cmd=None, …)` flag-only invocation (#672)
211202

@@ -217,10 +208,9 @@ key argument.
217208
#### `Server.list_buffers(format_string=, filter=)` (#672)
218209

219210
{meth}`~libtmux.Server.list_buffers` gains ``format_string`` (``-F``)
220-
and ``filter`` (``-f``) kwargs. Callers can project a chosen template
221-
(e.g. ``"#{buffer_name}"``) or push a buffer-name match expression
222-
into tmux's format engine — same bad-filter caveat as the
223-
``search_*`` methods.
211+
and ``filter`` (``-f``) kwargs. Callers can ask tmux to return selected
212+
fields (e.g. ``"#{buffer_name}"``) or only buffers matching an expression —
213+
same bad-filter caveat as the ``search_*`` methods.
224214

225215
#### `Server.run_shell(cwd=, show_stderr=)` (#672)
226216

@@ -237,31 +227,22 @@ returns bytes tmux has read from the pane but not yet committed to
237227
the terminal — useful for diagnosing programs whose output stalls
238228
mid-sequence.
239229

240-
#### Scope-aware format-token retrieval (#672)
230+
#### More format-token fields on tmux objects (#672)
241231

242-
The ``-F`` template libtmux sends to each ``list-*`` subcommand is now
243-
scope- and version-aware. tmux's format engine cascades context
244-
downward from client → session → current window → active pane, so a
245-
``Session`` row hydrates active-window and active-pane fields via that
246-
cascade, and a ``Client`` row likewise hydrates the client's attached
247-
session, window, and active pane. ``client_*`` tokens resolve only
248-
under ``list-clients`` because tmux has no reverse cascade. Tokens
249-
introduced after tmux 3.2a are gated through ``FIELD_VERSION`` so the
250-
format string stays compatible with the project's minimum supported
251-
tmux. Tokens the running tmux doesn't recognize stay ``None`` on the
252-
typed surface — no crash, no warning.
232+
libtmux now asks each ``list-*`` subcommand for the format tokens that make
233+
sense for that object and tmux version. Tokens the running tmux does not
234+
support stay ``None`` instead of making the listing fail.
253235

254236
{class}`~libtmux.Pane`, {class}`~libtmux.Window`,
255-
{class}`~libtmux.Session`, and {class}`~libtmux.Client` declare typed
256-
dataclass fields for the scope-relevant tokens that ship in tmux 3.2a,
257-
including pane state (``pane_dead``, ``pane_in_mode``, ``pane_marked``,
258-
``pane_synchronized``, ``pane_path``, ``pane_pipe`` …), window state
259-
(``window_zoomed_flag``, ``window_silence_flag``, ``window_flags`` …),
260-
session state (``session_marked`` …), and the client view
261-
(``client_session``, ``client_readonly``, ``client_termtype`` …). Typed
262-
fields for tokens tmux added in 3.4 / 3.5 / 3.6 and the forward-looking
263-
set from tmux master will land in a follow-up shipment once those
264-
releases can be validated end-to-end.
237+
{class}`~libtmux.Session`, and {class}`~libtmux.Client` expose more typed
238+
attributes for pane state (``pane_dead``, ``pane_in_mode``, ``pane_marked``,
239+
``pane_synchronized``, ``pane_path``, ``pane_pipe``), window state
240+
(``window_zoomed_flag``, ``window_silence_flag``, ``window_flags``), session
241+
state (``session_marked``), and client state (``client_session``,
242+
``client_readonly``, ``client_termtype``). Some fields describe the active
243+
child object tmux reports with the row: for example, ``session.pane_id`` is
244+
the active pane in the session's current window, not a separate "session
245+
pane." See {ref}`format-tokens` for details.
265246

266247
### Fixes
267248

@@ -272,10 +253,9 @@ releases can be validated end-to-end.
272253
### Documentation
273254

274255
- New API page: {doc}`api/libtmux.client`.
275-
- {class}`~libtmux.neo.Obj`'s class docstring documents the
276-
downward-cascade resolution target so readers know that, for
277-
example, ``session.pane_id`` is the session's *current window's
278-
active* pane — not "the session's pane" (#672).
256+
- New {ref}`format-tokens` topic explains why some fields describe an active
257+
child object, such as ``session.pane_id`` reflecting the active pane in the
258+
session's current window (#672).
279259

280260
## libtmux 0.56.0 (2026-05-10)
281261

MIGRATION

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,16 @@ _Detailed migration steps for the next version will be posted here._
117117

118118
### `LibTmuxException` `str()` gains a subcommand prefix
119119

120-
When {exc}`~libtmux.exc.LibTmuxException` is raised from one of the
121-
typed command wrappers, ``str(exc)`` now starts with the originating
122-
tmux subcommand name followed by ``": "``. ``exc.args[0]`` still
123-
carries the raw tmux output, and the new
120+
When {exc}`~libtmux.exc.LibTmuxException` is raised from a libtmux method,
121+
``str(exc)`` now starts with the originating tmux subcommand name followed
122+
by ``": "``. ``exc.args[0]`` still
123+
carries the original tmux error text, and the new
124124
{attr}`~libtmux.exc.LibTmuxException.subcommand` attribute exposes
125125
the tmux subcommand name on its own.
126126

127-
The wrapped-stderr *type* changed: ``exc.args[0]`` is now ``str``,
127+
The error-payload *type* changed: ``exc.args[0]`` is now ``str``,
128128
joined with ``"\n"`` when tmux emitted multiple stderr lines.
129-
Previously it was ``list[str]`` — the raw ``proc.stderr`` was
130-
passed through to :class:`Exception` directly.
129+
Previously it was ``list[str]``.
131130

132131
**Who is affected:** code that pattern-matches `str(exc)` exactly,
133132
anchors a regex with `^` against the previous shape, or indexes
@@ -155,7 +154,7 @@ except LibTmuxException as exc:
155154
handle_missing_last_window()
156155
```
157156

158-
**Or — match against the raw stderr in `exc.args[0]`:**
157+
**Or — match against the original tmux error text in `exc.args[0]`:**
159158

160159
```python
161160
try:
@@ -170,20 +169,19 @@ except LibTmuxException as exc:
170169
{class}`~libtmux.Client` is new in 0.57.0, so this isn't a behavior
171170
change — but new users of {attr}`~libtmux.Server.clients` should know
172171
that ``client.session_id``, ``client.window_id``, and
173-
``client.pane_id`` are hydrated from tmux's downward format cascade
174-
(``c->session````s->curw````wl->window->active``) at the moment
175-
the {class}`~libtmux.Client` was built. They go stale as soon as the
176-
client switches sessions, changes window, or detaches.
172+
``client.pane_id`` reflect the client's attached view at the moment the
173+
{class}`~libtmux.Client` was read. They go stale as soon as the client
174+
switches sessions, changes window, or detaches.
177175

178176
For typed access that reflects the client's *live* attachment, use
179177
{attr}`~libtmux.Client.attached_session`,
180178
{attr}`~libtmux.Client.attached_window`, and
181179
{attr}`~libtmux.Client.attached_pane`:
182180

183181
```python
184-
# Snapshot (cheap, may be stale)
182+
# Snapshot (may be stale)
185183
client = server.clients.get(client_name=ctl.client_name)
186-
session_id = client.session_id # str captured at hydration time
184+
session_id = client.session_id # str captured when the object was read
187185

188186
# Live (re-reads list-clients; returns None if tmux no longer reports the client)
189187
attached = client.attached_session # libtmux.Session | None
@@ -198,19 +196,16 @@ the client's *stable* identifier and does not have this caveat. The
198196
{meth}`~libtmux.Client.from_client_name` calls still raise when that
199197
client row is gone.
200198

201-
{attr}`~libtmux.Client.attached_pane` is session-scope: it returns
202-
the session's current window's active pane, not the client's
203-
``CLIENT_ACTIVEPANE`` focus. The two diverge once a client has used
204-
``select-pane -P`` to set its own active pane.
199+
{attr}`~libtmux.Client.attached_pane` follows the attached session's current
200+
window. That can differ from the per-client active pane set by
201+
``select-pane -P``.
205202

206-
### `Pane.reset` now dispatches through `self.server.cmd`
203+
### `Pane.reset` now uses one tmux command sequence
207204

208205
{meth}`~libtmux.Pane.reset` now bundles ``send-keys -R`` and
209-
``clear-history`` into a single tmux IPC routed through
210-
``self.server.cmd`` (with an explicit ``-t <pane_id>`` on both
211-
subcommands) rather than calling ``self.cmd`` twice. This closes a race
212-
where output written to the pane between the two IPCs could land in the
213-
scrollback that the second call then cleared.
206+
``clear-history`` into one tmux command sequence targeted at the pane.
207+
This closes a race where output written between separate commands could
208+
land in the scrollback that the second command then cleared.
214209

215210
**Who is affected:** test fixtures and downstream code that intercepts
216211
``Pane.cmd`` (for example with ``unittest.mock.patch.object(pane,
@@ -223,7 +218,7 @@ The three ``display_message`` wrappers now report tmux stderr via
223218
:func:`warnings.warn` rather than raising
224219
{exc}`~libtmux.exc.LibTmuxException`. tmux uses stderr for both genuine
225220
errors and informational messages, and the right escalation depends on
226-
tmux version and call shape; the wrappers default to warning so callers
221+
tmux version and requested format; the wrappers default to warning so callers
227222
can decide. To escalate to an exception, wrap the call in
228223
:func:`warnings.catch_warnings` with ``filterwarnings("error")``:
229224

0 commit comments

Comments
 (0)