Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c30b376
common(feat[logging]): One producer for tmux_cmd
tony Aug 22, 2026
395982d
libtmux(refactor[logging]): Share the extra builder
tony Aug 22, 2026
e88e312
docs(feat[logging]): Document the logging schema
tony Aug 22, 2026
133127a
Server(fix[kill_session]): Log the session it killed
tony Aug 22, 2026
099f8f7
pytest_plugin(fix[session]): Keep fixtures out of reports
tony Aug 22, 2026
16ad7d7
common(fix[logging]): Name the caller in failure records
tony Aug 22, 2026
c8fd26e
docs(fix[logging]): Note what WARNING now covers
tony Aug 22, 2026
1e2e324
common(fix[logging]): Keep a record on one line
tony Aug 22, 2026
e14348c
common(fix[logging]): Cap the logged command line
tony Aug 22, 2026
6bfac74
docs(logging): Show how list-valued keys render
tony Aug 22, 2026
3c1142d
docs(logging): State the cap's limit honestly
tony Aug 22, 2026
331d321
common(fix[logging]): Hide env values coming back too
tony Aug 22, 2026
b356422
common(fix[logging]): Log content length, not content
tony Aug 22, 2026
f342e7e
docs(logging): Say what makes DEBUG expensive
tony Aug 22, 2026
4ac9963
docs(logging): Leave the logger as the example found it
tony Aug 22, 2026
d4136bd
neo(fix[logging]): Report a row libtmux cannot parse
tony Aug 22, 2026
3610a79
docs(logging): Cover the advisories logging never sees
tony Aug 22, 2026
717b42a
logging: Drop a key that served no observed case
tony Aug 22, 2026
2240986
libtmux(fix[logging]): Close record safety gaps
tony Aug 22, 2026
bf489f0
docs(fix[logging]): Match the hardened contract
tony Aug 22, 2026
261050b
libtmux(fix[logging]): Close policy gaps
tony Aug 22, 2026
13e0a91
docs(fix[logging]): Clarify policy boundaries
tony Aug 22, 2026
5f2d6d5
docs(logging): Move the schema to the package guide
tony Aug 22, 2026
4a1c6de
libtmux(refactor[logging]): Log only swallowed failures
tony Aug 22, 2026
d5c9b7e
libtmux(refactor[logging]): Omit command payloads from records
tony Aug 22, 2026
458dfac
tests(refactor[logging]): Collapse logging coverage
tony Aug 22, 2026
f695385
docs(refactor[logging]): Focus the logging contract
tony Aug 22, 2026
eff189a
libtmux(fix[logging]): Normalize launch failures
tony Aug 23, 2026
0539bb2
libtmux(fix[logging]): Escape lifecycle context
tony Aug 23, 2026
c2be589
libtmux(fix[logging]): Preserve launch failures
tony Aug 23, 2026
88bc96a
tests(fix[logging]): Make lifecycle test portable
tony Aug 23, 2026
0e26cc0
logging(fix[payloads]): Restore log payloads
tony Aug 23, 2026
2defd84
tmux(fix[errors]): Preserve launch diagnostics
tony Aug 23, 2026
c8c8d6e
tests(fix[control-mode]): Read buffered output
tony Aug 23, 2026
8504144
tmux(fix[errors]): Keep resource errors native
tony Aug 23, 2026
080c95a
tests(fix[logging]): Cover normal record variants
tony Aug 23, 2026
54016f3
docs(fix[logging]): Clarify handler ownership
tony Aug 23, 2026
83013b3
docs(CHANGES): Logging and failure contracts
tony Aug 23, 2026
d265786
tests(fix[logging]): Pin query record pair
tony Aug 23, 2026
22ecfca
docs(fix[logging]): List hook warnings
tony Aug 23, 2026
d192d0f
docs(CHANGES): Note query logger migration
tony Aug 23, 2026
f8b0a61
docs(CHANGES): Scope the command logger migration
tony Aug 24, 2026
e9ad992
docs(MIGRATION): Note failure and logging changes
tony Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,105 @@ $ uvx --from 'libtmux' --prerelease allow python
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### Breaking changes

#### Query records use the command logger (#748)

Calls through {func}`~libtmux.neo.fetch_objs` no longer emit the `tmux list
queried` and `tmux list parsed` `DEBUG` records from `libtmux.neo`. They produce
`tmux command dispatched` and `tmux command completed` records from
`libtmux.common` instead. Applications that configure module-specific logging
now select the command logger:

```python
import logging

# Before
query_logger = logging.getLogger("libtmux.neo")

# After
query_logger = logging.getLogger("libtmux.common")
```

`libtmux.common` carries every tmux subprocess rather than list queries alone.
Filter a handler on the `tmux_subcommand` field to isolate queries again;
{ref}`logging` shows the filter.

#### Raised failures stay in the exception channel (#748)

A failure that libtmux propagates or translates no longer emits an `ERROR`
record first. Applications that consumed those records now log at their own
exception boundary.

```python
import logging

from libtmux import exc

logger = logging.getLogger(__name__)

# Before
operation()

# After
try:
operation()
except exc.LibTmuxException:
logger.exception("libtmux operation failed")
raise
```

#### Unusable tmux executables share one exception (#748)

Missing, non-executable, and malformed tmux executables now raise
{exc}`~libtmux.exc.TmuxCommandNotFound` from
{class}`~libtmux.common.tmux_cmd` and
{meth}`Server.raise_if_dead() <libtmux.Server.raise_if_dead>`. Attempted
launches preserve the operating-system message and cause. Other launch
failures remain native `OSError` values.

```python
available = True
try:
server.raise_if_dead()
# Before: except (exc.TmuxCommandNotFound, OSError):
# After:
except exc.TmuxCommandNotFound:
available = False
```

### What's new

#### Structured records identify tmux work (#748)

Command `DEBUG` records add best-effort subcommand and socket fields while
retaining the complete command, exit status, bounded output snapshots, and
line counts. Lifecycle `INFO` records consistently identify affected sockets,
sessions, windows, panes, and targets.

Applications can route and format these fields independently through
standard-library handlers without modifying the original records.

#### Handled failures produce one diagnostic (#748)

{attr}`Server.sessions <libtmux.Server.sessions>`,
{attr}`Server.attached_sessions <libtmux.Server.attached_sessions>`, and
{attr}`Server.clients <libtmux.Server.clients>` now emit one `ERROR` record
when they convert a tmux execution failure to an empty result. Expected probes
and failures returned to callers stay quiet.

Malformed `terminal-features`, `terminal-overrides`, and `command-alias`
entries produce one aggregate `WARNING` per option with the skipped-entry
count.

### Fixes

#### Caller-controlled log context stays on one line (#748)

Command arguments, socket labels, object identities, targets, and failed
lookup paths escape control characters before rendering, so one value cannot
forge additional log lines.

### Documentation

#### Cleaner `from_env` examples (#719)
Expand All @@ -57,6 +156,12 @@ environment-setup plumbing, so each example leads with the constructor call it
demonstrates instead of the socket-path and `$TMUX` boilerplate needed to run
it.

#### Logging configuration and record reference (#748)

{ref}`logging` documents logger ownership, levels, structured fields, failure
routing, payload policy, handler filtering and formatting, and `caplog`
assertions.

### Development

#### CI actions updated to current majors
Expand Down
105 changes: 105 additions & 0 deletions MIGRATION
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,111 @@ sections below for detailed migration examples and code samples.
_Detailed migration steps for the next version will be posted here._
<!-- END PLACEHOLDER - ADD NEW MIGRATION ENTRIES BELOW THIS LINE -->

## libtmux 0.63.x: Failure and logging channels (#748)

### An unusable tmux binary raises `TmuxCommandNotFound`

{class}`~libtmux.common.tmux_cmd` and
{meth}`Server.raise_if_dead() <libtmux.Server.raise_if_dead>` translate a
missing, non-executable, or malformed tmux executable into
{exc}`~libtmux.exc.TmuxCommandNotFound`. That exception subclasses
{exc}`~libtmux.exc.LibTmuxException`, not `OSError`. Through 0.62.0 a
non-executable binary surfaced as `PermissionError` and a malformed one as
`OSError`, so a handler written for either class stops firing.

**Who is affected:** code catching `OSError` or `PermissionError` around a
tmux launch. Launch failures carrying any other errno still propagate as a
native `OSError`.

**Before:**

```python
try:
server.raise_if_dead()
except OSError:
available = False
```

**After:**

```python
from libtmux import exc

try:
server.raise_if_dead()
except exc.TmuxCommandNotFound:
available = False
```

The operating system's diagnostic survives the translation whenever the launch
was attempted: the message carries it and `__cause__` holds the original
`OSError`. A tmux binary absent from `PATH` is never launched, so its
`__cause__` is `None`.

```python
from libtmux import exc

try:
server.raise_if_dead()
except exc.TmuxCommandNotFound as error:
errno = getattr(error.__cause__, "errno", None)
```

### Raised failures no longer emit an `ERROR` record

Through 0.62.0 libtmux logged an `ERROR` record before propagating or
translating a failure. A raised failure now reaches the caller through the
exception channel alone. `ERROR` records mark the opposite case, a boundary
that swallowed a failure and returned an empty result:
{attr}`Server.sessions <libtmux.Server.sessions>`,
{attr}`Server.attached_sessions <libtmux.Server.attached_sessions>`, and
{attr}`Server.clients <libtmux.Server.clients>`.

**Who is affected:** alerting and log scraping that counted libtmux `ERROR`
records to detect failed operations. Log at your own exception boundary:

```python
import logging

from libtmux import exc

logger = logging.getLogger(__name__)

try:
operation()
except exc.LibTmuxException:
logger.exception("libtmux operation failed")
raise
```

### Query `DEBUG` records come from `libtmux.common`

{func}`~libtmux.neo.fetch_objs`, the query path behind the object list
accessors, emitted `tmux list queried` and `tmux list parsed` on `libtmux.neo`
through 0.62.0. The shared command producer emits `tmux command dispatched`
and `tmux command completed` on `libtmux.common` in their place.

**Who is affected:** logging configuration naming `libtmux.neo`, and
assertions on the old message text. `libtmux.common` carries every tmux
subprocess rather than list queries alone; filter a handler on the
`tmux_subcommand` field to isolate queries. See {ref}`logging`.

**Before:**

```python
import logging

logging.getLogger("libtmux.neo").setLevel(logging.DEBUG)
```

**After:**

```python
import logging

logging.getLogger("libtmux.common").setLevel(logging.DEBUG)
```

## libtmux 0.62.0: Query exceptions join the hierarchy (#718)

{exc}`~libtmux.exc.ObjectDoesNotExist` and
Expand Down
1 change: 1 addition & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def add_doctest_fixtures(
session=session,
)
doctest_namespace["monkeypatch"] = request.getfixturevalue("monkeypatch")
doctest_namespace["caplog"] = request.getfixturevalue("caplog")


@pytest.fixture(autouse=True)
Expand Down
8 changes: 8 additions & 0 deletions docs/topics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ Create sessions, windows, and panes programmatically.
Common patterns for scripting and automation.
:::

:::{grid-item-card} Logging
:link: logging
:link-type: doc
Structured log records, the `tmux_` field schema, and application-owned
handlers.
:::

:::{grid-item-card} Context Managers
:link: context_managers
:link-type: doc
Expand Down Expand Up @@ -96,6 +103,7 @@ floating_panes
workspace_setup
automation_patterns
context_managers
logging
options_and_hooks
clients
format-tokens
Expand Down
Loading
Loading