# libtmux for Lua > The Lua port of libtmux (libtmux). Every code sample below is Lua; the same pages exist for the other nine ports under their own prefix. - [Lua API reference](https://libtmux.org/en/lua/latest/reference/): every public symbol, generated from the source. Hosted on libtmux.org. --- # Concepts Source: https://libtmux.org/en/lua/latest/concepts/ > tmux objects, command transports, queries, and workspaces across the libtmux language ports. libtmux lets you create sessions, arrange windows and panes, send commands, and read output from tmux. Start with the object hierarchy, then read about the transport, query, or workspace behavior your program needs. These pages explain shared concepts and differences between language ports: - **[Server, session, window, pane](server-session-window-pane/)**: tmux's object hierarchy and attached clients. - **[Control mode vs one-shot](transports/)**: subprocess commands, persistent connections, and batching. - **[Filtering and queries](queries/)**: find objects and handle absent or ambiguous matches. - **[Workspaces](workspaces/)**: build pane layouts from code or configuration files. Use the header's port links to open documentation for your language, including its API reference. --- # Server, session, window, pane Source: https://libtmux.org/en/lua/latest/concepts/server-session-window-pane/ > The object hierarchy every libtmux port mirrors from tmux itself, and the client that sits outside it. libtmux models tmux's server, session, window, and pane objects: ``` Server ├── Session │ └── Window │ └── Pane └── Client (attached view) ``` A `Server` contains sessions. Each `Session` contains links to windows, and each `Window` contains panes. Commands run inside a `Pane`, where you send input and capture output. A window can be linked to more than one session. ## Stable identity, not name or index tmux assigns a unique ID to each session, window, and pane at creation. The ID remains stable for that object's lifetime even if its name or index changes: | Object | ID prefix | Example | |--------|-----------|---------| | Session | `$` | `$13` | | Window | `@` | `@3243` | | Pane | `%` | `%5433` | | Server | - | identified by socket name or path instead | Python handles use the object ID to refresh their fields. Immutable snapshots, such as those in TypeScript, Swift, and Go, use IDs to identify the same tmux object across reads. Walking the whole tree, in each port: ## Client: a view, not a child A `Client` represents a terminal attached to a session. Several clients can view the same server, and each can switch sessions or windows independently. Client fields describe the view at the time of the read. A [control-mode connection](../transports/) is also a client. It appears in `list-clients`, counts toward `session_attached`, and affects attachment-dependent behavior such as `destroy-unattached`. Closing the last attached client can therefore destroy a session configured with that option. ## What differs between ports Ports differ in how they read state and report failures: - **Refreshing state.** Python objects reflect their last read until you call `.refresh()`. TypeScript, Swift, and Go also provide snapshots whose relationships can be queried without another tmux command. - **Blocking and async calls.** Python and Java use blocking calls. Rust, TypeScript, .NET, and Swift provide async APIs. Go uses ordinary calls with contexts for cancellation and deadlines. - **Failure handling.** Python and .NET raise exceptions. C++ returns `expected`. Some commands have an expected negative answer, such as `has-session` when a session is absent; check the method's result contract before treating that answer as a failure. See [Control mode vs one-shot](../transports/) for command costs and connection behavior. --- # Control mode vs one-shot Source: https://libtmux.org/en/lua/latest/concepts/transports/ > How a call in your program actually reaches the tmux server, and why a port might give you a choice. libtmux sends commands to tmux through subprocesses or persistent control-mode connections. Some ports also batch commands into one invocation: 1. **One-shot subprocess.** Each command spawns a fresh `tmux` process, which parses argv, executes the command, prints its output, and exits. This is the default in every port, and the *only* lane in Python: every `.cmd()` call underneath the object API is a `subprocess.Popen` around a `tmux` invocation. 2. **A persistent control-mode client.** `tmux -C attach-session` starts one long-lived tmux process that stays attached and speaks a line-oriented protocol over its stdout: commands go in, replies and asynchronous notifications (`%window-add`, `%output`, ...) come out, without starting a process per call. 3. **One invocation, several commands.** tmux accepts more than one command per invocation (`;`-joined, or one `-F`-tagged `list-*` per line). A port can fold several logical operations into a single process start without opening a control-mode connection at all. ## Where each port draws the line | Port | One-shot | Folded invocation | Persistent control client | |------|----------|--------------------|-----------------------------| | Python | every call | - | test-only (`ControlMode`, `libtmux._internal`) | | TypeScript | default | `pipeline()`, `batch()` | `connect()` / `watch()`: notifications only, commands stay per-process | | Go | `process` path | `Plan.Run` | `connection` (`Session.OpenControl`), `streaming` (`OpenNotifications`) | | Rust | `plan` feature, sequential | `plan`, folded | `control-mode` feature | | C# | "One-shot" mode | "Chained" mode (`server.Chain()`) | "Control" mode (`EnterControlModeAsync`) | | C++ | bounded subprocess (default) | `Chain` | `Server::control()` → `Connection` | | Java | every call | `Batch` | `ControlClient` (`attach`, `send`, `subscribeEvents`) | | Swift | default | - | `server.connect()` / `.watch()` (notifications; see below) | Choose based on whether you need command results, notifications, or a batch of changes. ## Notifications and commands are separable TypeScript's `connect()` adds an event observer while commands such as `session.newWindow(...)` and `pane.sendKeys(...)` continue to run as separate tmux processes. A dedicated process provides a completion boundary for output from alias-expanded or waiting commands. Go's `Session.OpenControl`, .NET's `EnterControlModeAsync`, Java's `ControlClient.send`, and Rust's `control-mode` feature can send commands through the persistent connection. Check your port's transport API before assuming that subscribing to events also changes how commands run. ## A control client is a real client A persistent control connection attaches a tmux client. It appears in `list-clients`, increments `session_attached`, and affects `destroy-unattached`, client hooks, and idle-client accounting. Each connection counts separately. Python's internal `ControlMode` test helper uses this behavior for commands that require an attached client, such as `display-popup` and `detach-client`. ## Why fold several commands into one invocation Creating an object can require a second command to read its resulting state. Batching reduces those repeated reads and process starts. TypeScript's `batch()` resolves planned mutations from one final snapshot. Go's `Plan`, .NET's `Chain`, and C++'s `Chain` also group operations without attaching a control client. ## What this costs in practice The Rust `matrix` example and .NET README compare process counts and timings. Their results describe specific workloads and environments: - **Rust's** `matrix` example runs the same create-and-query workload five ways. Blocking sequential and async sequential both cost 6 processes for 6 dispatches; folding the same 6 into async batches costs 3 processes; routing them over a control-mode connection costs exactly 1. - **C#'s** README reports the *marginal* cost of one more command in each mode, as medians against tmux 3.7b: roughly 2.3 ms for another one-shot process, roughly 0.2 ms for another command over an already-open control client, and roughly 0.02 ms for another command folded into one chained invocation. A persistent connection avoids starting a client for each command. A chain groups a known sequence into one invocation. For occasional commands, use the default subprocess transport; measure your workload before changing transports for performance. Use a notification stream when your program needs tmux events. ## Choosing a lane These examples use each port's subprocess API. See the port reference for batching and control-mode setup. To inspect tmux's control protocol, attach a control client: ```console $ tmux -C attach-session -t work ``` --- # Filtering and queries Source: https://libtmux.org/en/lua/latest/concepts/queries/ > How you get from every session on the server to the one pane you mean, and what happens when zero or several match. Use a collection filter to find matching sessions, windows, or panes. Use an exactly-one lookup when your next operation requires a single target. - **Filtering returns a collection; exactly-one lookup checks the result count.** A `.filter()` or `.where()` call returns zero or more matches. Methods such as `.get()`, `.one()`, and `Selections.exactlyOne()` return one object or report a missing or ambiguous match. - **Choose where to filter.** Filter a snapshot in your program when you need several queries over the same data. A tmux format filter can reduce the rows returned by a live read. The cost depends on the data and queries you need. ## Python: `.filter()` and `.get()`, Django-style `server.sessions`, `session.windows`, and `window.panes` are `QueryList` collections. Call `.filter()` with field names and optional lookup suffixes: Lookups include `exact`, `contains`, `startswith`, `endswith`, `regex`, and their case-insensitive `i`-prefixed variants. Multiple keywords and chained `.filter()` calls combine with AND. `.get()` requires exactly one match. Its `default` argument handles an absent result; multiple matches still raise `MultipleObjectsReturned`. Server-wide collections (`server.windows`, `server.panes`) enumerate window links. A window linked to two sessions appears once per session, so a lookup can be ambiguous even when the window ID is unique. Use `Window.linked_sessions` to find its sessions. For a known ID, use `Pane.from_pane_id()` or `Window.from_window_id()` to resolve the object directly. For servers with hundreds or thousands of panes, `.filter()` still builds every object before you discard the ones that don't match. `search_sessions`, `search_windows`, and `search_panes` push a tmux `-f` filter expression down to the server instead, so libtmux builds objects only for the matches: Python-side lookups work with the library's supported tmux versions. The tmux filter grammar requires tmux 3.2 or newer. An unknown format token expands to an empty value, so a malformed filter can look like a valid filter with no matches. If `search_*()` unexpectedly returns no results, try `#{m:*,#{session_name}}` to check that the session data is available. ## TypeScript: criteria as data TypeScript's `Selection.where()` accepts structured, serializable criteria that can be stored in a configuration file or sent through MCP: `some`, `every`, and `none` test related objects. `{ mode: "insensitive" }` enables case-insensitive comparison. Use `.where()` for criteria that can be encoded with `encodeWhereDocument` and decoded with `decodeWhereDocument`; use `.filter()` for a predicate function. `.one()` throws `NoMatchError` or `MultipleMatchesError`. `.oneOrUndefined()` permits an absent result. ## Go, Rust, Java, C++: typed fields that fail queries at compile time These ports use typed fields to reject invalid comparisons at compile time: - **Go** offers both `tmux.PaneFilter{Active: tmux.Ptr(true), ...}` structs that push down into `SearchPanes` (one tmux command, only matches returned), and a `snapshot()` read followed by `tmuxq.Where(panes, predicate)` when you want several answers from one read. - **Rust** uses typed fields: `fields.pane_active.eq(true)` is valid, but `.gt(...)` on that boolean field is not. Expressions compose with `.and()`. With the `serde` feature, a query can be encoded as a versioned JSON document for configuration or MCP. - **Java** exposes each field as a typed accessor (`Pane_.index()`, `Session_.name()`) that plugs straight into an ordinary `Stream.filter()`; `Pane_.index().startsWith("2")` doesn't compile because the index is a number, not a string. `Selections.exactlyOne(...)` is the `.get()`-shaped call, throwing `NoMatchException` or `MultipleMatchesException`. - **C++** composes `FilterExpr` values with `&&`, `||`, and `!`, as in `pane::command.starts_with("nv") && pane::active`. Invalid field operations such as `pane::active.starts_with("x")` fail to compile. Examples of typed and local filters: ## The cardinality contract, side by side | Port | Collection filter | Exactly-one | Empty | Several | |------|--------------------|--------------|-------|---------| | Python | `.filter()` | `.get()` | `ObjectDoesNotExist` (or `default=`) | `MultipleObjectsReturned` | | TypeScript | `.where()` / `.filter()` | `.one()` | `NoMatchError` (or `.oneOrUndefined()`) | `MultipleMatchesError` | | Java | `Stream.filter()` | `Selections.exactlyOne()` | `NoMatchException` | `MultipleMatchesException` | See the Go, Rust, C++, .NET, and Swift references for their exactly-one result types and failure handling. --- # Workspaces Source: https://libtmux.org/en/lua/latest/concepts/workspaces/ > Build pane layouts with the object API or a workspace configuration file. A workspace arranges windows and panes for a task, such as editing code, running a development server, and following logs. Build it with the object API when the layout depends on program logic. Use a declarative builder when you want to store the layout in a configuration file, such as [tmuxp](https://tmuxp.git-pull.com/) YAML or JSON. ## Building one imperatively Create a window, split it into panes, apply a layout, and send each pane its command: `Window.split()` or `Pane.split()` adds a pane. Direction and size control its placement. `select_layout()` rearranges the panes while their processes continue running. tmux provides `even-horizontal`, `even-vertical`, `main-horizontal`, `main-vertical`, and `tiled` layouts. Python's `attach=False` and C++'s detached creation keep new windows in the background. Check the creation defaults for your port if focus matters. Splits and resizes require tmux commands; [Control mode vs one-shot](../transports/) covers their transport costs and batching. ## Building one declaratively These packages read or build workspace configurations based on tmuxp: | Port | Package | Shape | |------|---------|-------| | Python | tmuxp itself | the format this whole idea is named after | | TypeScript | `@libtmux/workspace` | `applyWorkspace(server, { session_name, windows: [...] })` | | Go | `workspace` | tmuxp-shaped, per the port's own module layout | | Rust | `tmux-workspace` | tmuxp-shaped | | Java | `libtmux-workspace` | "enough of tmuxp's format to describe a workspace" | | C# | `LibTmux.Workspace` | reads tmuxp YAML directly | | Swift | `TmuxWorkspace` | Swift, JSON, or YAML (YAML needs the `YAMLWorkspaces` trait) | TypeScript's `applyWorkspace` applies a desired configuration. Applying the same configuration again reuses its existing objects: C++ provides a consumer example in `examples/workspace/` that reads tmuxp configuration. The workspace builder is part of that example, rather than a library package: ## Cleaning up For temporary workspaces, Python's `Window` and `Session` context managers kill their objects on block exit, including when the block raises: See [Context managers](/topics/context-managers/) for cleanup support in each port. Use explicit kill methods when the handle does not provide scope-based cleanup. --- # Examples Source: https://libtmux.org/en/lua/latest/examples/ > Programs for sending input, capturing output, and building workspaces in each language. These examples show how to complete a tmux task in each language. Use the tabs to select your port. Each page includes the source file and the checks run by that port's test suite; see those details before adapting an excerpt into a standalone program. Examples marked with `file=` are read from the port source during the build. Other blocks are copied excerpts. The source details identify which mechanism each block uses. - **[Attach and send keys](attach-and-send-keys/)**: get a session, send a command, and read output. Uses the hierarchy described in [Server, session, window, pane](/concepts/server-session-window-pane/). - **[Capture pane output](capture-pane-output/)**: read back what a pane is showing, and wait for output to appear instead of guessing a delay. The companion to [Capturing output](/guides/capturing-output/). - **[Build a workspace from a file](workspace-from-file/)**: the tmuxp-shaped job of describing a multi-window session as data and building it in one call, in each port that has one. ## What "verified" means, per port Port repositories use the following checks for their source examples. This site reads or copies those examples; a successful site build alone does not execute them: | Port | Mechanism | What it checks | |------|-----------|-----------------| | Python | `pytest` `testpaths` includes `README.md` and `src/libtmux` | `>>>` doctest blocks run against a real, isolated tmux session on every test run | | TypeScript | `scripts/check-doc-runnable.ts` | A block tagged `` must appear, line for line, in that file, which the integration suite executes | | Go | `go generate ./tmux` (`internal/generate/docs`) | A `` region in `README.md` is rewritten from the matching `// docs:name` … `// docs:end` region in `examples/`; CI fails on drift | | Rust | `#![doc = include_str!("../README.md")]` | The entire README is a doc comment, so `cargo test --doc` compiles and runs every fenced Rust block in it | | Java | `docs-tests` (`./gradlew :docs-tests:test`) | Every Java fence in READMEs and guides is compiled against the real artifacts, then run against real tmux via `libtmux-junit5` | | .NET | `sync_snippets.py --check` + `ReadmeExampleTests` | A `` region is quoted from a tested `[Example]` method; every `csharp run` block is additionally compiled and executed | | C++ | `tools/docs/check_readme.py` | Each ` ```cpp ` block in `README.md` must appear verbatim as a `#region` in `examples/05-readme.cpp`, which CTest builds and runs | | Swift | `Scripts/check_examples.py` | Each ` ```swift ` block in `README.md` and product READMEs must appear in `Examples/Sources/`, which `swift test --package-path Examples` compiles through its public products | See [Testing with libtmux](/guides/testing-with-libtmux/) for the fixture each of those test suites runs against, and each example page below for the exact file a given snippet was quoted from. --- # Attach and send keys Source: https://libtmux.org/en/lua/latest/examples/attach-and-send-keys/ > Get a session handle, send a command to a pane, and capture output. Get a session handle, send a command to a pane, and capture output. These examples use libtmux from your program; to attach your terminal interactively, see [Attaching to tmux](/guides/attaching-to-tmux/). Select your port below. The examples retain their source's setup, error handling, and cleanup, so the operations shown vary by port. [Where this comes from](#where-this-comes-from) identifies each source and its test coverage. ## Finding an existing session instead For a script that runs repeatedly, look up a session before creating it. [Attaching to tmux](/guides/attaching-to-tmux/#finding-a-session-instead-of-always-creating-one) shows that pattern, and [Filtering and querying, in practice](/guides/querying-and-filtering/) covers absent and ambiguous matches. ## Where this comes from ### Python **Source:** `src/libtmux/server.py`, `session.py`, `pane.py` docstrings **In this page:** hand-quoted, composed from three separate docstrings **Checked by:** `pytest` runs every `>>>` doctest (`testpaths` includes `src/libtmux`) against a real, isolated tmux session on every test run ### TypeScript **Source:** `examples/quickstart/quickstart.ts` **In this page:** read whole from the file **Checked by:** run against real tmux by `bun test examples`; its first half is also mirrored into README.md under a `` marker, checked line-for-line by `scripts/check-doc-runnable.ts` ### Rust **Source:** `crates/libtmux/examples/scratch.rs` **In this page:** read whole from the file **Checked by:** run to completion against a throwaway tmux by `scripts/run-examples.sh` (`just examples`), which CI runs and which also asserts the example leaves no session behind ### Go **Source:** `examples/quickstart/main.go` **In this page:** read whole from the file **Checked by:** the whole file runs against a real tmux server as `TestQuickstart`; the `docs:quickstart` region inside it is additionally mirrored into README.md by `go generate ./tmux`, and CI fails if the two drift ### Java **Source:** `examples/src/main/java/io/github/libtmux/examples/BuildAWorkspace.java` **In this page:** read whole from the file **Checked by:** run against real tmux by the `examples` module's own `ExamplesRunTest` ### .NET **Source:** `examples/LibTmux.Examples/Snippets/OneShot.cs` **In this page:** read whole from the file **Checked by:** its `ConnectAndBuild` region is mirrored into README.md and checked by `sync_snippets.py --check`; the mirrored `csharp run` block is additionally compiled and run by `ReadmeExampleTests` ### C++ **Source:** `examples/05-readme.cpp`, the `connect` and `build` regions **In this page:** Copied excerpts from the `connect` and `build` regions. **Checked by:** quoted verbatim into README.md, checked for drift by `tools/docs/check_readme.py`, and the whole file is built and run by CTest ### Swift **Source:** `Examples/Sources/ExampleCode/Changing.swift` **In this page:** read whole from the file **Checked by:** matched against the README's "Change what is there" section by `Scripts/check_examples.py`; compiled and run through the package's public products by `swift test --package-path Examples` ### Source inclusion A `file="..."` fence reads the named source during the site build. Hand-quoted excerpts are copies; their source files and regions are listed above. --- # Capture pane output Source: https://libtmux.org/en/lua/latest/examples/capture-pane-output/ > Capture a pane's screen and wait for expected output or a completion signal. Read a pane after [sending input](../attach-and-send-keys/). [Sending keys](/guides/sending-keys/#the-race-you-cant-see-from-the-call-site) explains why an immediate capture can miss output. These examples show screen capture and waiting; [Capturing output](/guides/capturing-output/) explains the choices. See [source details](#where-this-comes-from) for each example's source and validation. ## Read what's on screen The .NET example under "Wait for text instead of guessing a delay" uses `Pane.CaptureAsync` within a wait. Its separate Psmux transport also provides a capture API, shown in `examples/LibTmux.Examples/Snippets/Psmux.cs`. ## Wait for text instead of guessing a delay The Python example uses `wait_for`, tmux's signal channel. It waits for a signal from the command rather than matching pane text. `Session.OpenNotifications` receives tmux events as a stream. For tests that need to wait for screen text, use `tmuxtest.WaitForText`; see [Testing with libtmux](/guides/testing-with-libtmux/). Rust's `wait_for_text` looks before it sleeps, joins wrapped lines so a needle spanning a wrap still matches, and returns `PaneWait::Dead` rather than hanging forever if the pane's process ends first. Attach the `ControlClient` to receive `%output` notifications. An unattached client receives command replies only. `TmuxWait.UntilAsync` polls a read against a predicate rather than sleeping a fixed amount. C++ has no checked snippet that waits on pane *text*. `Server::wait_for(channel, timeout)`, in `include/libtmux/server.hpp`, uses tmux's own `wait-for` signal instead of scraping output, and its doc comment explains why that is the safer choice when the command you are waiting on can be made to announce itself: "a server that dies under a waiter makes tmux exit zero, which is indistinguishable from being signalled ... this reports that as a failure instead." `waitForOutput` takes patterns for both success and failure, so a process that fails fast is discovered immediately rather than by timing out. ## Where this comes from ### Python **Source:** `src/libtmux/pane.py` (`capture_pane`), `src/libtmux/server.py` (`wait_for`) docstrings **In this page:** hand-quoted **Checked by:** `pytest` runs every `>>>` doctest against a real, isolated tmux session on every test run ### TypeScript **Source:** `examples/capture/capture.ts` (read), `examples/agent/agent.ts` (wait) **In this page:** read whole from each file **Checked by:** both run against real tmux by `bun test examples`; `agent.ts` is additionally mirrored into README.md under a `` marker checked by `scripts/check-doc-runnable.ts` ### Go **Source:** `examples/quickstart/main.go` (read, already shown whole on the previous page), `examples/control-mode-subscribe/main.go` (wait) **In this page:** read: hand-quoted; wait: read whole from the file **Checked by:** both run against real tmux as `TestQuickstart` / `TestControlModeSubscribe`; the wait file's `docs:watching` region is additionally mirrored into README.md by `go generate ./tmux` ### Rust **Source:** `crates/libtmux/examples/scratch.rs`, already shown whole on the previous page **In this page:** hand-quoted excerpts of the same file **Checked by:** run to completion against a throwaway tmux by `scripts/run-examples.sh`, which CI runs ### Java **Source:** root `README.md` Quickstart (read), `examples/src/main/java/io/github/libtmux/examples/WatchPaneOutput.java` (wait) **In this page:** read: hand-quoted; wait: read whole from the file **Checked by:** every README fence is compiled and run against real tmux by `docs-tests`; `WatchPaneOutput` is additionally run by the `examples` module's `ExamplesRunTest` ### .NET **Source:** root `README.md`, "Running something, and reading it back" **In this page:** hand-quoted **Checked by:** one of the `csharp run` blocks compiled and run against real tmux by `ReadmeExampleTests` ### C++ **Source:** `examples/05-readme.cpp` `capture` region (read); `include/libtmux/server.hpp` doc comment (wait, no fence) **In this page:** hand-quoted **Checked by:** the `capture` region is quoted verbatim into README.md and checked by `tools/docs/check_readme.py`; the whole file is built and run by CTest ### Swift **Source:** `Examples/Sources/ExampleCode/Changing.swift` (read, already shown whole on the previous page), `Waiting.swift` (wait) **In this page:** read: hand-quoted excerpt; wait: read whole from the file **Checked by:** both matched against the README by `Scripts/check_examples.py` and run by `swift test --package-path Examples` ### Source inclusion Go, Rust, and Swift each reuse a file already shown in full on [Attach and send keys](../attach-and-send-keys/#where-this-comes-from): rather than dump the same file a second time, this page quotes just the relevant lines by hand, with a comment naming the source, and points back at the full listing there. --- # Build a workspace from a file Source: https://libtmux.org/en/lua/latest/examples/workspace-from-file/ > The tmuxp-shaped job of describing a multi-window session as data and building it in one call, in each port that has a checked way to do it. [tmuxp](https://tmuxp.git-pull.com/) describes sessions, windows, panes, and shell commands in configuration files. Several libtmux ports provide builders for this format. [Source details](#where-this-comes-from) identify the example files and their checks. For Python, use [tmuxp](https://tmuxp.git-pull.com/), a separate application built on libtmux's `Server`, `Session`, `Window`, and `Pane` APIs. TypeScript's `applyWorkspace` reuses existing objects when the same configuration is applied again. Go's `Example()`, in `workspace/example_test.go`, is a Go `Example` function: `go test` runs it and checks its output against the `// Output:` comment at the end, so this is executed on every test run rather than merely present in a README. `Parse` rejects a field it doesn't recognize rather than dropping it silently, and reports every problem it finds at once with the line it's on. `Build` is not atomic: tmux has no transaction, so a failure partway through leaves whatever was already created in place, identified by the session `Build` still returns. Rust's `freeze(&session).await?` exports an existing session to the workspace format. It recovers windows, panes, and working directories, but cannot recover the shell command originally typed to start a process. Java's `read` and `parse` validate the configuration and return a `Workspace` value. Only `build` changes tmux state. The .NET builder can wait for shell readiness before sending commands; [Sending keys](/guides/sending-keys/#the-race-you-cant-see-from-the-call-site) explains the startup race. It polls `pane_current_command`, `cursor_x`, and `cursor_y` for up to ten seconds by default. `PaneReadiness.Auto` waits for zsh, `Always` waits for every pane running the session's default shell, and `Never` sends immediately. If `BuildAsync` fails partway through, `WorkspaceBuildException.PartialResult` identifies what was created. C++'s `examples/workspace/` implements a consumer of the core API with its own `workspace.hpp` and `tmuxp.hpp` types. Those types are part of the example, not the library package. See [the example's README](https://github.com/libtmux/libtmux-cxx/tree/main/examples/workspace) to adapt it. Swift's `WorkspaceBuilder.build` rejects an existing session with the requested name. `Workspace.decode(yaml:)` reads tmuxp YAML when the `YAMLWorkspaces` trait is enabled. `Workspace.decode(json:)` needs no additional trait. ## Where this comes from ### Python **Source:** Not listed. **In this page:** no fence; the README says tmuxp is a separate project by design **Checked by:** n/a ### TypeScript **Source:** `examples/workspace/workspace.ts` (`@libtmux/workspace`) **In this page:** read whole from the file **Checked by:** run against real tmux by `bun test examples/workspace` ### Go **Source:** `workspace/example_test.go` (`workspace.Parse` / `workspace.Build`) **In this page:** read whole from the file **Checked by:** `Example()` and its siblings run under `go test` and are checked against their own `// Output:` comments ### Rust **Source:** `crates/tmux-workspace/README.md`, "Build it" **In this page:** hand-quoted **Checked by:** the crate's own `crates/tmux-workspace/src/lib.rs` includes the README as a doc comment (`#![doc = include_str!("../README.md")]`), so `cargo test --doc` runs this exact block ### Java **Source:** `libtmux-workspace/README.md`, "What you get back" **In this page:** hand-quoted **Checked by:** every Java fence in the module's README is compiled and run against real tmux by `docs-tests` ### .NET **Source:** `src/LibTmux.Workspace/README.md` **In this page:** hand-quoted **Checked by:** one of the READMEs and docs `ReadmeExampleTests` compiles and runs against real tmux ### C++ **Source:** `examples/workspace/` (a consumer, not a library API) **In this page:** prose only **Checked by:** `examples/workspace/tests/` runs it against real tmux; `ctest -R consumer.workspace` selects it. It exercises the example's own types, not a published `libtmux` API ### Swift **Source:** `Examples/Sources/ExampleCode/Workspaces.swift` **In this page:** read whole from the file **Checked by:** matched against the README's "Workspaces, from a file or from Swift" section by `Scripts/check_examples.py`; compiled and run by `swift test --package-path Examples` ### Source inclusion Rust, Java, and .NET use copied excerpts from their README examples. The source details above identify those files and their checks. --- # Guides Source: https://libtmux.org/en/lua/latest/guides/ > Task-oriented walkthroughs that sit between the concepts and each port's own API reference. Use these guides to connect to tmux, send input, capture output, query objects, and test your program. [Concepts](/concepts/) explains the object model and transport choices. - **[Getting started](getting-started/)**: install tmux, choose a port, and run an example. - **[Attaching to tmux](attaching-to-tmux/)**: select a socket and find or create a session. - **[Sending keys](sending-keys/)**: send literal text, named keys, and Enter. - **[Capturing output](capturing-output/)**: read the screen or scrollback and wait for a result. - **[Filtering and querying, in practice](querying-and-filtering/)**: apply the lookup contracts from [Filtering and queries](/concepts/queries/). - **[Testing with libtmux](testing-with-libtmux/)**: use isolated tmux servers and manage test cleanup. [Examples](/examples/) provides source-backed programs for the same tasks, with source and validation details on each page. --- # Getting started Source: https://libtmux.org/en/lua/latest/guides/getting-started/ > Install tmux, pick a port, and run the smallest thing that proves your setup works. ## Install tmux The common tmux baseline documented here is 3.2a. Individual features can require a newer release; check your port's compatibility notes. Confirm your installed version: ```console $ tmux -V ``` If `tmux` is missing or older than 3.2a, install a supported version with your platform's package manager. libtmux uses an installed tmux executable. ## Pick a port Choose the port for your project's language: [Python](/py/), [TypeScript](/ts/), [Rust](/rs/), [Go](/go/), [Java and Kotlin](/java/), [.NET](/dotnet/), [C++](/cxx/), or [Swift](/swift/). [Server, session, window, pane](/concepts/server-session-window-pane/) explains the shared model, and [Control mode vs one-shot](/concepts/transports/) covers transport differences. For a prerelease package, pin an exact version and check its release notes before upgrading. API availability and defaults can differ between ports. ## Run the smallest thing that proves it works Start a tmux session to connect to: in one terminal: ```console $ tmux new-session -s foo -n bar ``` In a second terminal, install your port's package and run its example. The Python example uses the `foo` session above; the other examples create their own sessions. Installation commands appear in comments at the start of each block. [Attach and send keys](/examples/attach-and-send-keys/) provides the full examples, their source files, and their validation details. ## What just happened A server handle targets tmux without taking over your terminal. Creating a session starts the server if needed. Sending keys writes input to a pane; the method's Enter and literal-text options control how tmux interprets it. [Sending keys](../sending-keys/) explains those defaults. Capture methods read the pane's screen or a requested scrollback range. ## Where to go next - [Concepts](/concepts/) for the mental model behind what you just did: the object hierarchy, how commands actually reach tmux, and how filtering works once you have more than one session to choose from. - [Attaching to tmux](../attaching-to-tmux/), [Sending keys](../sending-keys/), and [Capturing output](../capturing-output/) go one level deeper into each half of the round trip you just ran. - [Attach and send keys](/examples/attach-and-send-keys/) for the fully checked version of every block above, and how each one is verified. - Your port's own API reference (via the port switcher) once you're ready to look up method details. --- # Attaching to tmux Source: https://libtmux.org/en/lua/latest/guides/attaching-to-tmux/ > What a plain constructor call actually connects to, and how to find a session that might already exist instead of always creating a new one. Obtain a server and session handle to control tmux from your program. Your process keeps its own stdin and stdout, and tmux continues running independently. [Attach and send keys](/examples/attach-and-send-keys/) demonstrates this workflow. Attaching your terminal is a separate operation. Python's `Session.attach()` runs `tmux attach-session` and hands the terminal to tmux. [tmuxp](https://tmuxp.git-pull.com/) uses it after building a workspace. Check your port's reference if your program needs to hand over the terminal. ## Which socket a bare constructor reaches Use an explicit socket when several tmux servers may be running. The examples below show each constructor's defaults and environment-aware alternatives. To locate the server from inside a pane, use the port's environment lookup API for `TMUX` and `TMUX_PANE`. Sources: Python's `Server.from_env()` is doctested in `src/libtmux/server.py` (`pyproject.toml` `testpaths`). .NET's resolution order and `FromEnvironment` are from `src/LibTmux/README.md` ("Where a bare connect lands"), one of the nine documents `ReadmeExampleTests` compiles and runs. Go's is `tmux/server_options.go`'s doc comments. Rust's fallback is `crates/libtmux/examples/find.rs`, run via `cargo run --example find`. C++'s four constructors are the README's own description of `Server`, at the top of "What is libtmux?". Swift's is `README.md`'s top-level quickstart, matched against `Examples/Sources/QuickStart/main.swift` by `Scripts/check_examples.py`. Java's is `README.md`, run by [`docs-tests`](../testing-with-libtmux/#java-docs-tests). ## Finding a session instead of always creating one A script that runs more than once usually wants "attach if a session by this name already exists, create it otherwise," not a fresh session every time. Sources: Go uses `examples/filter-query/main.go`, region `docs:query-in-tmux`. Java uses `examples/.../BuildAWorkspace.java`, run by the examples module's tests. For exactly-one lookup semantics, see [Filtering and querying, in practice](../querying-and-filtering/). Swift uses `Examples/Sources/ExampleCode/Querying.swift`, checked against the README by `Scripts/check_examples.py` and exercised by `Examples/Tests/ExampleTests/ModeTests.swift`. .NET exposes `Server.HasSessionAsync(name)` in `src/LibTmux/Server.Lifecycle.cs`. Its `CreateSessionAsync` supports `ReplaceExisting`, which kills and recreates the session. Rust and C++ provide query APIs for session lookup. See [Filtering and querying, in practice](../querying-and-filtering/) and the port references for the call signatures; this page has no tested excerpt for those combinations. ## Where to go next - [Sending keys](../sending-keys/) and [Capturing output](../capturing-output/) pick up once you have a pane handle. - [Attach and send keys](/examples/attach-and-send-keys/) has the full, sourced code for the round trip this guide assumes. - [Testing with libtmux](../testing-with-libtmux/) if the server you want to attach to is one your own test suite should own and tear down. --- # Sending keys Source: https://libtmux.org/en/lua/latest/guides/sending-keys/ > Literal text versus tmux key names, whether Enter is pressed for you, and why a command can outrun the shell about to run it. Send literal text to type characters into a pane, or send tmux key names such as `C-c`, `Enter`, and `Up` to press those keys. Check the method's literal-text and Enter defaults: typing the word `Enter` and pressing Enter are different operations. ## Literal text, key names, and whether Enter follows These examples show each port's text, named-key, and Enter behavior. [Attach and send keys](/examples/attach-and-send-keys/) provides the full source examples and validation details. Check each method's input contract before sending text that could be a key name. Some ports separate text and key-name methods; others use a literal-text flag. [Concepts](/concepts/) introduces the shared tmux model. ## The race you can't see from the call site Completing `send-keys` means tmux accepted the input. The shell may still be starting, and the command may still be running. The port examples provide different ways to wait: - **Rust** uses a `retry_until` loop in the README's query example to wait for the shell. - **Go** provides `tmuxtest.WaitForShellReady` for tests that need a ready shell. - **.NET** demonstrates waiting for command output in the README's "Running something, and reading it back" section. Wait for shell readiness before sending input when startup matters. Then wait for the command's expected output or a completion signal before reading its result. The next guide covers those waiting APIs. ## Where to go next - [Capturing output](../capturing-output/): reading back what you just sent, and waiting for it correctly instead of guessing a delay. - [Attach and send keys](/examples/attach-and-send-keys/): the full sourced round trip this guide picks apart piece by piece. --- # Capturing output Source: https://libtmux.org/en/lua/latest/guides/capturing-output/ > Read a pane's screen or scrollback and wait for output or a completion signal. Capture a pane to read its visible screen or scrollback. After [Sending keys](../sending-keys/), wait for the expected output or a completion signal before reading the result. ## Visible pane vs. scrollback `tmux capture-pane` distinguishes the currently visible screen from the scrollback history above it, and every port exposes that split rather than flattening it: Java's `pane.capture()` and .NET's `pane.CaptureAsync()` return the visible pane as a list of lines; neither's own README shows a scrollback option as of this page, so check the port's reference before assuming one exists. Sources: TypeScript's is `examples/capture/capture.ts`, run by `bun test examples/capture`. Go's is `examples/quickstart/main.go`. Rust's is `crates/libtmux/README.md`'s capability table, doctested via `#![doc = include_str!("../README.md")]`. C++'s is `README.md`'s "Read a pane" section, quoted verbatim from `examples/05-readme.cpp`'s `capture` region and checked by `tools/docs/check_readme.py`. Swift's is `README.md`, "Change what is there." ## Wait for the expected text An immediate capture can race the shell, as [Sending keys](../sending-keys/#the-race-you-cant-see-from-the-call-site) explains. Wait for the expected text with a timeout so your program stops promptly when the output arrives and reports a failure if it never does: Python's pytest plugin supplies isolated test servers; see [Testing with libtmux](../testing-with-libtmux/). For Python and C++ signal-based waiting, use the `wait-for` APIs below. This page does not include a wait-for-text helper for those ports. Sources: Go's is `tmux/tmuxtest/screen.go`, quoted in `README.md`'s "Testing your own code" section. Rust's is `crates/libtmux/README.md`, doctested. TypeScript's is `examples/agent/agent.ts`, run by the integration suite and quoted in `packages/libtmux/README.md` (``). Java's is `examples/.../WatchPaneOutput.java`. .NET's is `README.md`, one of the `csharp run` blocks `ReadmeExampleTests` runs. Swift's is `Examples/Sources/ExampleCode/Waiting.swift`, matched against `` and the README by `Scripts/check_examples.py`; the same file's `server.capture(pane, since: mark)`, called in a loop with the cursor it returns, is the "watch as it prints" shape for output too large or too open-ended to wait on a single pattern. ## When the pane can announce itself: `wait-for`, not scraping If you control the command, have it signal completion with `tmux wait-for -S done`. Wait on the same channel to avoid matching screen text: The Python example is a doctest in `src/libtmux/server.py`. C++'s `Server::wait_for(channel, timeout)`, declared in `include/libtmux/server.hpp`, also detects a server that dies during the wait. Swift's `server.wait(for:)` example waits for a build to signal completion: Source: `Examples/Sources/ExampleCode/Waiting.swift`. Rust's `crates/libtmux/README.md` documents the same pattern under "tmux keeps a signal nobody is waiting on, so the job finishing first does not lose the race, and nothing polls," runnable as `examples/orchestrate.rs`. ## Where to go next - [Filtering and querying, in practice](../querying-and-filtering/): once you're reading more than one pane, finding the right one to capture. - [Testing with libtmux](../testing-with-libtmux/): the isolated-server fixtures that make waiting on real tmux practical inside a test suite. - [Capture pane output](/examples/capture-pane-output/): the full sourced code for the patterns above. --- # Filtering and querying, in practice Source: https://libtmux.org/en/lua/latest/guides/querying-and-filtering/ > Filter tmux objects, require one match, and choose where a query runs. Find sessions, windows, or panes with collection filters and exactly-one lookups. [Filtering and queries](/concepts/queries/) explains the result-count contracts and the choice between local and tmux-side filtering. This guide adds examples for common queries. ## Filling in the rest of the cardinality table | Port | Collection filter | Exactly-one | Empty | Several | |------|--------------------|--------------|-------|---------| | Go | `tmuxq.Where(values, predicate)` | `tmuxq.ExactlyOne(values, predicate)` | `tmuxq.ErrNoMatch` | `tmuxq.ErrMultipleMatches` | | Rust | `.iter().matching(&expr)` | `.exactly_one()` | prints via the error's `Display` | same, one error type covers both | | C++ | pipe a range into [`libtmux::matching(expr)`](/cxx/latest/reference/libtmux-matching/) | `libtmux::exactly_one(range)` | `.error()` says which way it went wrong | same call, same error type | Go's `ExampleExactlyOne` in `tmuxq/example_test.go` checks the result with `go test` and `// Output:` assertions: Rust's is `examples/find.rs`, run via `cargo run --example find`: C++'s is quoted straight from `examples/05-readme.cpp`'s `cardinality` region into `README.md`, and `tools/docs/check_readme.py` fails the build if the two ever disagree: For .NET and Swift result-count handling, consult the port reference. The examples here demonstrate .NET's `IEnumerable.Matching(expression)` returning an `IReadOnlyList` and Swift's `hasSession(_:)` returning a `Bool`. The latter checks existence; see [Attaching to tmux](../attaching-to-tmux/#finding-a-session-instead-of-always-creating-one). ## Declarative filters that travel, beyond Python and TypeScript [Filtering and queries](/concepts/queries/) covers Python's `.filter()` lookups and TypeScript's `.where()` documents. Two more ports build the same "a query is data, not code" idea, verified against their own README: Sources: .NET's is `src/LibTmux/README.md`, "Filtering." Swift's is `Examples/Sources/ExampleCode/Filtering.swift`, matched against the README by `Scripts/check_examples.py`. ## Case-insensitive matching For case-insensitive matching in Java, .NET, Go, Rust, and C++, consult the port reference. Sources for the examples above: Python's lookup is covered in [Filtering and queries](../../concepts/queries/); TypeScript's is in `README.md`, "What querying looks like"; Swift's is in `Examples/Sources/ExampleCode/Filtering.swift`. ## Push the filter into tmux, or read once and filter locally Use a tmux-side filter to reduce the rows returned, or query a snapshot when you need several answers from one read. [Filtering and queries](/concepts/queries/) compares Python's `search_sessions()` with `.filter()`, and Go's `SearchPanes` with a snapshot plus `tmuxq.Where`. Check the required tmux version. Unknown format tokens expand to empty values, so validate an unexpectedly empty search before concluding that no objects match. ## Where to go next - [Attach and send keys](/examples/attach-and-send-keys/): its "Finding an existing session instead" section is this guide's recipes applied to one concrete lookup. - [Testing with libtmux](../testing-with-libtmux/): most of the fixtures there hand you a server with exactly one thing on it, which is precisely when an exactly-one query is the right tool instead of a filter you then index into. --- # Testing with libtmux Source: https://libtmux.org/en/lua/latest/guides/testing-with-libtmux/ > Every port ships a way to give your own tests a real, disposable tmux server. What each one hands you, and what it guarantees about cleanup. Use a private tmux server to test code that creates sessions, sends input, or captures output. The fixtures below allocate a separate socket and manage normal test cleanup. Cleanup after abrupt process termination depends on the fixture; Java's stale-server cleanup is described below. Request the fixture to obtain its server. Python's `session` fixture depends on `server`, so requesting a session also creates an isolated server: That exact block is a doctest in `src/libtmux/pytest_plugin.py`, checked by running it as a nested pytest run and asserting it passes. `session_params` overrides how the fixture builds a session (window size, for instance) without forking it; a temporary `HOME` and tmux config keep window and pane indices stable across machines, so an assertion like `window_name == "test"` doesn't depend on whatever `.tmux.conf` the test runner happens to have. `tmuxtest.NewServer(ctx, t)` captures the environment and working directory, resolves the tmux executable, and creates a server on its own socket. Construction can return an error. Test cleanup kills the server, and wait failures include the last captured screen. Source: `README.md`, "Testing your own code," backed by `tmux/tmuxtest/`. Enable the `test-support` feature in a dev-dependency. The crate README uses these guards in doctests through `#![doc = include_str!("../README.md")]`. `libtmux::test::retry_until(deadline, condition)` polls an arbitrary async condition; `Pane::wait_for_text` waits specifically for pane text. See [Capturing output](../capturing-output/). `libtmux-junit5` supplies each test with a running `Server` containing a session named `libtmux`. Request `TmuxSocketPath` when your code takes a socket path. Fixtures live in JUnit's per-test extension store. A shutdown hook kills servers owned by that JVM, and startup cleanup removes servers left by JVMs that have exited. Source: `libtmux-junit5/README.md`. The port's `docs-tests` module compiles Java fences from READMEs and guides, then runs them against `libtmux-junit5` servers. A `` directive can instead require a named exception, a compile failure, or an explicit skip reason. Source: `docs-tests/README.md`. `LibTmux.Testing` ships as a separate package, under `src/LibTmux.Testing/`. `await using` disposes the scope and kills its server when the block exits. Use `TmuxWait.UntilAsync` to wait for expected state; see [Capturing output](../capturing-output/). Source: `README.md`, "Testing your own code," exercised by `ReadmeExampleTests`. The example comes from `README.md`, "Testing your own tmux tools," and is checked against the `fixture` region in `examples/05-readme.cpp` by `tools/docs/check_readme.py`. Enable the `testing` CMake component with `find_package(libtmux COMPONENTS testing)`. It creates a private socket and temporary directory, sets `TMUX_TMPDIR`, and removes `TMUX` and `TMUX_PANE` from the child environment. `SocketNamespace::consumer(...)` labels sockets with the consumer suite's name. `examples/tests/README.md` shows use from outside the library's build tree. `TmuxFixture` is a separate package product. It starts a server with a bootstrap session and limits concurrent fixtures to reduce process and pseudo-terminal exhaustion. `LIBTMUX_TMUX_BIN` selects the executable; otherwise it checks installed locations. Source: `Tests/TmuxFixture/README.md`. TypeScript's harness at `packages/libtmux/src/_internal/test/testkit.ts` is internal and unpublished. For external tests, create an isolated `Server` and manage its cleanup in your test framework. ## Where to go next - [Capturing output](../capturing-output/): the wait helpers most of these fixtures are meant to be used alongside, instead of a fixed `sleep` in a test. - [Attach and send keys](/examples/attach-and-send-keys/) and [Capture pane output](/examples/capture-pane-output/): the same operations these fixtures give you a server to run, shown as tested examples in their own right. --- # Capture server state Source: https://libtmux.org/en/lua/latest/guides/snapshots/ > Source-owned Lua guide at 547c9c4228e0. Use `runtime:connect(options):await()` to bind a borrowed tmux daemon, then `server:snapshot(options):await()` to capture its state. Every live operation returns a Request. The [snapshot example](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/examples/snapshot.lua) prints each unique pane and its window ID using the public API. Pass an absolute `binary` and explicit absolute `socket_path` to `connect`. Optional `config_path` defaults to `/dev/null`. Connection options are copied before dispatch. The library does not select a default server or start one. Importing the library and inspecting captured records perform no I/O. Connection setup reads the actual daemon's version and identity. Linux socket binding creates a private socket alias in the selected socket's parent directory; that directory must permit creation and hard links. Each command checks the original socket and alias, reads bounded daemon evidence, and disables tmux autostart. A restart, replaced socket or uncertain connection invalidates the handle. Reconnect explicitly; old references do not bind to reused IDs. Tests currently cover Linux under WSL2. Native Linux and macOS remain separate unverified platform lanes. `server:close():await()` closes the handle and its owned connections. It leaves the borrowed daemon running. The runtime also closes handles when its root scope finishes. Keep live work inside that scope; returning a handle from `adapter.run` does not keep its runtime alive. ## Collections and projections Snapshots contain `sessions`, `windows`, `panes`, `window_links`, `clients` and `buffers` as [native selections](../query/). Canonical entity collections deduplicate identity; `snapshot.raw` preserves contextual listing rows and their order. A window linked twice has two link rows and one canonical window. Relationships refer only to captured data. Clients are the attached clients that tmux exposes through `list-clients`. Window links own `session_id`, `window_id`, `index` and active context. Pane and window IDs remain tmux strings; Lua positions are not tmux indexes. A daemon with no sessions can still have buffers. Capture reads them without creating a session. By default, capture loads every supported field in the [catalog](../fields/). The `fields` option maps collection names to nonempty field-name sequences; omitted collections keep their default projection. Required identity and relationship fields are added. `requested_projections` records the caller's selection; `projections` records effective fields, including for empty collections. Both maps use singular entity names such as `pane` and `window_link`. `capabilities.fields` records availability for the observed daemon version; it does not claim every tmux command is supported. Refresh by calling `snapshot` again. It returns fresh records. Tables remain mutable, so do not edit them during traversal. Editing a record does not refresh the server, another snapshot or its private identity index. Create a handle with `server:handle(snapshot, record)`. The record's kind picks the handle's class: a `SnapshotPane` gives a `libtmux.Pane`, a `SnapshotSession` a `libtmux.Session`, and so on, so LuaLS offers only the methods tmux accepts for that kind. It copies the record's private identity, so edits to exposed `id` or `ref` fields cannot redirect it. `handle:reference()` returns a separate reference table without I/O. `handle:snapshot():await()` explicitly captures fresh state and returns the matching record. A missing link/index, client/TTY or buffer name returns `target_missing`; stale server generations fail before capture. Same-name client or buffer reuse cannot establish continuous object identity. ## Consistency and limits Capture spans multiple commands. `acquisition.started` and `finished` are monotonic milliseconds. Normal capture reports observed relationship races in `races` and sets `complete` to false. Transport failures return an error. Set `strict = true` for one additional identity/topology verification pass. It compares membership and link context, ignoring listing order and volatile scalar values. A mismatch returns `inconsistent_snapshot` with the original snapshot in `err.partial`; capture never retries to manufacture consistency. `verification` records the pass and its result. Equal observations do not make capture atomic, prove continuous identity, or detect objects created and removed between reads. Replacing a named buffer can preserve every catalog field while changing its contents. Each pass permits at most `max_rows` raw rows (default 65,536) across all collections, before deduplication. `max_bytes` (default 16 MiB) limits both total encoded output and retained scalar/key bytes per pass. Strict mode adds one bounded pass. Accumulated rows and the graph copy also consume the runtime's byte budget; exhaustion returns `queue_full`. These counters bound data, not exact Lua heap allocation. `timeout` defaults to 750 milliseconds per listing command. Endpoint evidence checks have a separate bounded 750-millisecond deadline. Whole capture has a finite command count; cancel its Request for an earlier stop. Closing the server during capture prevents a successful current-reference result. Cancellation retires owned clients; it does not kill the daemon. Run the example against an explicitly selected existing server: ```console $ TMUX_BIN=/usr/bin/tmux TMUX_SOCKET=/tmp/example-tmux.sock lua examples/snapshot.lua ``` The package gate runs this file from installed core and luv outside the checkout, checks exact output, and verifies cleanup. Core-only installation still has no luv dependency; this standalone example selects it explicitly. --- # Compatibility targets Source: https://libtmux.org/en/lua/latest/guides/compatibility/ > Source-owned Lua guide at 547c9c4228e0. The Lua port is under development. The following versions define required acceptance tests; they are not a published support promise. A source declaration, successful import, or sibling-port result does not establish live compatibility. | Component | Required versions | | --- | --- | | PUC Lua | 5.1.5, 5.2.4, 5.3.6, 5.4.9, 5.5.1 | | LuaJIT | v2.1 at c6ffc141a8762b41703f9287d63d93622a13dd8f | | Standalone adapter | luv 1.52.1-0 built for each runtime ABI | | Neovim | 0.10.0, 0.10.4, 0.11.7, 0.12.5 | | tmux | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | | Platforms | Linux x86_64, macOS arm64; WSL2 recorded separately | Pure-core tests run once per runtime/platform. Standalone and embedded live tests cover floor/current tmux for each runtime or host. The full tmux sweep uses PUC Lua 5.5.1 and current Neovim. Consumer imports and pure validation cover each runtime; floor/current combinations also run interoperability and workspace failure tests. Client/daemon version mismatches must report protocol failures truthfully instead of inferring daemon capabilities from the client executable. macOS x86_64 and native Windows transport are outside the initial platform matrix. Local tmux 3.7d and 3.8-rc results are diagnostic evidence and cannot replace released-version checks. Neovim's LuaJIT results do not prove stock PUC Lua 5.1 yield behavior. A Neovim build using PUC Lua is a separate host gate. Named buffer deletion requires tmux 3.4 or later. The typed API refuses older releases because a missing target can delete another buffer. The older-release deletion parity requirement remains open; see [buffers](../buffers/). Exact operating-system images, binary hashes, compiler and module ABI identities belong with each result. Missing and skipped cells remain unverified. No complete product compatibility cell has passed yet. Current local implementation evidence comes from Ubuntu 24.04 under WSL2 on x86_64. Record it in the WSL2 lane; it does not close the native Linux or macOS gates. Selected PUC, LuaJIT, Neovim and tmux foundation tests have passed there while the complete product is still being implemented. The [CI workflow](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/workflows/ci.yml) covers Linux unit runtimes, floor/current outer gates and the released-tmux integration sweep. Its [setup and coverage](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/CONTRIBUTING.md#github-actions) use the same local gate commands. A passing run establishes those implemented checks at its tested revision; macOS, additional host/runtime combinations and unfinished product requirements remain open. Version evidence comes from [Lua's version history](https://www.lua.org/versions.html), [LuaJIT's release policy](https://luajit.org/status.html), the [luv release](https://github.com/luvit/luv/releases/tag/1.52.1-0), [Neovim's Lua contract](https://neovim.io/doc/user/lua/#lua-compat), and [tmux releases](https://github.com/tmux/tmux/releases). --- # Create sessions, windows and panes Source: https://libtmux.org/en/lua/latest/guides/creation/ > Source-owned Lua guide at 547c9c4228e0. `Server:new_session`, `Session:new_window` and `Pane:split` return a `Request`. Await inside the adapter's managed coroutine, or register `on_complete(value, err)`. See [connections](../snapshots/) and [runtime ownership](../runtime/) for setup and cancellation. The receipt contains `session`, `window`, `pane` and `window_link` handles made from tmux's returned IDs. Its `created` sequence names the objects this operation created: all four for a session, window/pane/link for a window, and only the pane for a split. Other handles describe the containing context. Creating a pane proves neither application readiness nor command success. ## Commands and literal values Pass `argv` for a literal command, or `shell` for explicitly authored tmux shell text. They are mutually exclusive. Multiple arguments use tmux's native argument execution. A singleton executable uses `/usr/bin/env --` to avoid tmux's single-argument shell interpretation; it requires that utility and rejects executable names containing `=`. Use explicit shell text for that case. Omitting both fields uses tmux's configured default command or shell. Names, directories and environment values stay literal, including tmux format-looking text. Session names reject `:`, `.`, and control bytes because tmux would otherwise change them. `environment` maps portable variable names to string values and applies to the created session or pane process through tmux's `-e` semantics. It does not change the caller's environment. An explicit `cwd` must be an absolute existing directory. The asynchronous preflight rejects a missing directory before sending the creation command. Filesystem changes after validation remain possible. Input is copied and validated before dispatch, including nested process options. ## Placement and selection Sessions start detached and accept `name`, `window_name`, `width` and `height`. Windows accept `name`, an optional nonnegative `index`, and `select`; their parent is the Session handle's exact ID. A supplied occupied index fails rather than replacing the existing window. Splits accept `direction` (`left`, `right`, `up` or `down`), optional positive `size` in cells or `percent` from 1 through 99, `full_size`, and `select`. The default direction is down. New windows and splits leave selection unchanged unless `select=true`. Handle references cannot be edited to redirect an operation. ## Errors and limits `process` accepts `timeout`, `deadline`, `max_output_bytes`, `kill_timeout` and `drain_timeout`; see [command completion](../commands/). It cannot replace the bound socket, process environment, input stream or working directory. Creation accepts at most 1024 argv items, 128 environment entries and one MiB of encoded input, subject to the runtime's shared byte capacity. Errors preserve whether the command was not sent, may have taken effect, or completed. A malformed receipt reports `invalid_result` with completed effect and retained command output. Canceling the tmux client cannot undo a creation already accepted by the daemon. Creation does not retry or remove partial state. Closing the Server connection leaves created sessions running. The [public creation fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/domain.lua) exercises the same API through luv and Neovim with literal arguments, environment and directory values, returned IDs, explicit shell text and missing directories. --- # Execute tmux commands Source: https://libtmux.org/en/lua/latest/guides/commands/ > Source-owned Lua guide at 547c9c4228e0. After [connecting](../snapshots/), `server:command(argv, options)` submits literal tmux arguments and returns a Request. It preserves raw stdout/stderr bytes, exit code and signal, and waits for the owned client and its pipes to retire. A completed nonzero exit is returned as result data. Spawn, timeout, output-limit and incomplete-drain failures return `nil, err`. No shell parses argv. tmux still parses its own command syntax; the library protects literal separator arguments. A tmux command that explicitly accepts shell text, such as `run-shell`, retains that command's shell semantics. Native command aliases apply even to full built-in names, including commands used by typed domain methods. Hooks can run additional commands and affect state. The library does not change borrowed server configuration or promise that aliases preserve built-in semantics. A preliminary configuration check cannot prevent an alias from changing before a later command is parsed. Use `server:group(commands, options)` for an explicit ordered tmux command group. Its result is aggregate output and exit status. Parse failure can reject the whole group, immediate execution failure skips later commands, and delayed WAIT-command failures can still allow later commands. A group provides neither transactions nor independently attributed member results. Output routing also follows tmux: `run-shell` without a pane target writes job output to pane view mode on tmux 3.3–3.4; tmux 3.2a and 3.5 onward write it to the waiting client's stdout. An explicit `-t` pane target selects pane output. Without `-b`, client completion still waits for the job, and a control-mode `%end` marker can precede that completion. See the upstream [stdout restoration](https://github.com/tmux/tmux/commit/fb37d52ddeccb603b0932b81cff3a6228f1fd83d). Use `server:batch(commands, { concurrency = n, process = options })` for independent commands. Concurrency defaults to one and is bounded at 128; runtime capacity can reduce the active pool. The result is an input-indexed array of `completed`, `failed`, `unknown` or `skipped` outcomes, each with `effect` and optional `value`/`error`. Nonzero exits are failed outcomes with their actual result in `error.partial`. Other commands continue. A canceled batch exposes a frozen `err.partial.outcomes` receipt; it preserves completed results and distinguishes started work from work never sent. All commands and process options are validated and copied before dispatch. One submission accepts at most 1,024 commands, 4,096 total arguments and 16 MiB of input. NUL is rejected in argv and environment entries; raw stdin can contain it. Never retry mutations automatically, especially after an unknown effect. Canceling a client does not prove daemon work stopped. Process options include `stdin`, `cwd`, explicit `env` entries, `timeout`, monotonic `deadline`, `max_output_bytes` (default 1 MiB), `drain_timeout` (250 ms), and `kill_timeout` (100 ms). Timeout/deadline apply to the client operation; pinned endpoint evidence uses a separate bounded check. Output limits and runtime byte admission remain distinct. Excess batch output is reported with byte counts and truncation metadata; it is not silently kept. The raw API is an escape hatch. Named domain operations, owned command completion and observation APIs are still under development. A successful `send-keys` process does not report the exit of a pane application. --- # libtmux for Lua Source: https://libtmux.org/en/lua/latest/guides/overview/ > Source-owned Lua guide at 547c9c4228e0. Script tmux from Lua: create sessions and panes, capture terminal output, query server state, and watch pane output. Use standalone Lua with luv or Neovim's event loop. **Alpha software.** The core API is still changing. The separate MCP and workspace packages are unpublished scaffolds. [Install](#install) · [Read a server](#read-a-server) · [Query](#query-captured-state) · [Create panes](#create-sessions-and-panes) · [Neovim](#neovim) · [Guides](#guides) · [CI](https://github.com/libtmux/libtmux-lua/actions/workflows/ci.yml) · [MIT license](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/LICENSE) ## Install Install the core alpha with LuaRocks configured for your Lua interpreter: ```console $ luarocks --local install libtmux 0.1.0alpha1-1 ``` To install from a checkout: ```console $ luarocks --local make rockspecs/libtmux-scm-1.rockspec ``` See the [changelog](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/CHANGES.md) and [release instructions](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/CONTRIBUTING.md#releases) for release history and verification. Linux is tested; macOS remains unverified. Add the local rocks tree to Lua's module paths: ```console $ eval "$(luarocks --local path)" ``` For standalone scripts, also install luv. Building it requires a C compiler and CMake. Neovim provides its own libuv binding. ```console $ luarocks --local install luv 1.52.1-0 ``` Core and local queries require only Lua. Importing a module does not start tmux or an event loop. CI runs unit tests on Lua 5.1–5.5 and LuaJIT, plus live tests across tmux 3.2a–3.7c. See the [compatibility matrix](../compatibility/) for exact versions and remaining platform coverage. ## Read a server Connect to an existing server by its explicit socket path. This is the full [snapshot example](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/examples/snapshot.lua), which prints pane and window IDs: ```lua local adapter = require("libtmux.runtime.luv") local function must(value, err) if err ~= nil then error(err, 0) end return value end must(adapter.run(function(runtime) local server = must(runtime :connect({ binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable"), socket_path = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to an explicit socket"), }) :await()) local snapshot = must(server:snapshot({ strict = true }):await()) for _, pane in ipairs(snapshot.panes) do io.stdout:write(pane.id, "\t", pane.window_id, "\n") end must(server:close():await()) return true end)) ``` Live operations return Requests; `:await()` yields inside the runtime body and returns `value, err`. The `must` helper propagates errors. Closing the connection leaves the tmux server and its sessions running. Set `TMUX_BIN` and `TMUX_SOCKET` to absolute paths for your server, then run: ```console $ lua examples/snapshot.lua ```
Try it on a temporary server Run from the checkout after installing the dependencies above. This starts a private tmux server and removes it when the example finishes. ```console $ sh <<'SH' set -eu unset TMUX TMUX_PANE TMUX_BIN=$(command -v tmux) demo_dir=$(mktemp -d /tmp/libtmux-lua-XXXXXX) TMUX_SOCKET="$demo_dir/tmux.sock" export TMUX_BIN TMUX_SOCKET cleanup() { "$TMUX_BIN" -S "$TMUX_SOCKET" kill-server 2>/dev/null || true rm -rf "$demo_dir" } trap cleanup EXIT "$TMUX_BIN" -f /dev/null -S "$TMUX_SOCKET" new-session -d -s demo lua examples/snapshot.lua SH ```
## Query captured state After capturing `snapshot` in the example above, filter its panes with structured criteria or an ordinary Lua function: ```lua local editors = snapshot.panes:where({ current_command = { one_of = { "nvim", "vim" } }, }) local inactive = snapshot.panes:filter(function(pane) return not pane.active end) print(#editors, #inactive) for _, pane in ipairs(editors) do print(pane.id, pane.current_command) end ``` Selections are dense, one-based Lua tables. Both queries read the snapshot without calling tmux. Capture again to refresh it; a snapshot spans multiple tmux commands and is not an atomic view. See [criteria, relationships and live queries](../query/), the [field reference](../fields/), or run the [standalone table-query example](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/examples/native_query.lua): ```console $ lua examples/native_query.lua ``` ## Create sessions and panes Before closing `server` in that runtime body, create a session and split a window. These calls use the same `must` helper: ```lua local work = must(server:new_session({ name = "work" }):await()) local editor = must(work.session:new_window({ name = "editor" }):await()) local split = must(editor.pane:split({ direction = "right", percent = 40 }):await()) must(split.pane:send_text("printf '%s\\n' hello"):await()) must(split.pane:send_keys({ "Enter" }):await()) ``` Creation returns a table with `session`, `window`, `pane` and `window_link` handles. These operations leave the new sessions and panes running. Sending keys confirms that tmux accepted input; it does not wait for a shell command to finish. See [creation](../creation/) and [pane operations](../panes/) for literal argv, capture, resize and cleanup. ## Neovim From the checkout, start Neovim with the library on its `runtimepath`: ```console $ nvim --cmd 'set runtimepath+=.' ``` With `TMUX_SOCKET` set to an existing server's absolute socket path, run this Lua code. `start` uses the editor's loop and reports the result in a callback: ```lua local adapter = require("libtmux.runtime.nvim") adapter.start(function(runtime) local server, err = runtime:connect({ binary = vim.fn.exepath("tmux"), socket_path = assert(os.getenv("TMUX_SOCKET")), }):await() if err then return nil, err end return server:snapshot({ strict = true }):await() end, function(snapshot, err) if err then vim.notify(tostring(err), vim.log.levels.ERROR) return end vim.notify(("Panes: %d"):format(#snapshot.panes)) end) ``` The runtime closes its connections when the body finishes and leaves the tmux server running. See [runtime ownership and cancellation](../runtime/). ## Guides - **Clients:** [switch sessions and detach terminals](../clients/). - **Read and watch:** [snapshots](../snapshots/), [session notifications and pane streams](../control/). - **Run and arrange:** [commands and batches](../commands/), [session/window topology](../topology/), [binary buffers](../buffers/). - **Configure:** [options and hooks](../settings/), [option reference](../options/), [environment values](../environment/). - **Contribute:** [setup and validation](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/CONTRIBUTING.md), [writing conventions](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/WRITING.md). Live tests use private sockets and clean up their own servers. See the contributing guide for the same offline checks that run in CI. --- # Observing a session Source: https://libtmux.org/en/lua/latest/guides/control/ > Source-owned Lua guide at 547c9c4228e0. `server:observe(session, options)` returns a Request for an observation lease. Pass a session handle from that Server's snapshot or creation result. Concurrent leases for the same session share one owned persistent client. General commands use the [process command API](../commands/). Opening verifies the pinned daemon before spawning and checks daemon evidence again through the new connection. Every client uses `-N`; a missing session fails without creating a session or linking a window. Each endpoint owns at most eight observation clients. The observation offers: - `watch_pane(pane, options)` returns a ready pane-output watch for a pane handle. - `watch_notifications(options)` returns a ready notification watch, preserving unknown events and raw lines. - `subscribe_format(pane, field_names, options)` returns a typed native format watch using generated pane fields. - `coverage()` returns copied session/pane coverage and connection generation. - `close()` closes this lease's watches; final close waits for native cleanup. Handles must belong to the same Server. Their copied private identities select targets; overwriting a public `reference` method cannot redirect observation. Options are copied on submission. Shared connection limits must agree across leases; conflicting options return `option_conflict`. Separate Server handles have independent pools even when their socket paths match. Canceling acquisition releases only that caller's claim. Startup continues for remaining callers; canceling the final claim closes its client. Acquisition during final cleanup returns `closing`. Await final `close()` before reopening; a new connection has a distinct generation and requires new watches. A failed startup also retains its pool entry until native cleanup finishes. A new acquisition on a failed shared connection returns its recorded loss error. Close the old leases before opening a fresh connection. Watch creation installs its local receiver before a same-connection `list-panes` coverage check. A pane outside that session returns `uncovered_pane`. Topology changes invalidate reported coverage and close affected pane watches with `observation_gap`; opening a new watch performs another bounded check. These checks are observations, not a topology transaction. Capture remains a separate operation with no claimed lossless handoff to the stream. ## Reading and closing `watch:next({ timeout = milliseconds })` returns a Request for one event. An absolute monotonic `deadline` is also supported. Only one read may be pending on a watch; another returns `concurrent_read`. Canceling a read detaches that waiter and leaves the watch usable. `watch:close()` is explicit and idempotent. An explicitly closed watch returns `nil, nil`; connection failure or loss returns `nil, err`. Pane events carry `kind = "output"`, `pane`, `data`, `sequence`, `generation` and `server_generation`. `data` is a Lua byte string, including NUL and non-UTF-8 bytes. Extended output also preserves its decimal `age` and `metadata`. A sequence identifies ordering within one connection; it does not imply complete pane history or application completion. Format events carry `kind = "format"`, a typed `value`, and explicit `session_id`, `window_id`, window-link `index` and `pane` context. Only catalog field names are accepted. Unknown or unsupported fields fail before writes; caller text cannot introduce a format expression. Registration acknowledgement establishes readiness. Native values arrive on tmux's [one-second sampling timer](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/control.c), so readiness does not wait for the first sample. Native title normalization and other daemon field behavior remain visible in the returned values. Closing a format watch unregisters its subscription. ## Bounds and ownership Defaults are 128 pending housekeeping replies, 128 watches, 4 MiB of retained connection data, and 1 MiB or 1,024 events per watch. Each observation claim, shared attachment and public watch consumes a runtime resource slot; the default runtime limit is 128 resources in total. Runtime byte and logical request limits also apply. Watch byte accounting includes event metadata; subscription projections and queued encoded commands are charged before deferred work begins. Overflow closes the affected watch with `observation_gap`. Its partial result reports discarded event count and decoded pane-byte count. Other watches and the shared reader continue. Native `%pause` also invalidates pane continuity. There is no silent drop policy, output coalescing, or claim that tmux pause is lossless backpressure. The reader parses bounded slices independently of watch consumption. Completion callbacks run through the runtime scheduler; application callbacks must not block the shared Lua event loop. A persistent connection occupies a resource lease, not an active process slot. A pending `next` uses the separate bounded logical-request lane. The serialized writer accepts only private housekeeping operations. Arguments are encoded for tmux's control-line parser. Bootstrap has its own response; subsequent replies use FIFO attribution and matching guard tuples. A request canceled after a possible write leaves a connection-owned tombstone until its reply drains. A write or framing failure closes the connection and fails pending callers. No command or pane input is retried or replayed. Normal root return closes resource leases after owned requests retire. Endpoint close or generation invalidation also closes its observation clients. Native cleanup waits for exit, pipe closure and pending write callbacks. Post-exit pipe drain is bounded at 250 ms. Explicit close first ends stdin; after 100 ms it signals only the owned client, escalating after another 100 ms. It never signals the tmux daemon or a pane program. ## Attachment effects The client uses `attach-session -E -f ignore-size,active-pane` with an exact session ID. It issues no shared pane/window selection, resizing, detachment or session-environment updates. Native client listings, attachment state, focus hooks and configured lifecycle policy remain observable. Hooks may themselves change state; preservation fixtures use neutral hooks. `focus-events` stays unchanged. Disabling it cannot suppress every attachment focus hook, and tmux provides no supported client flag for that guarantee. The transport does not create hidden sessions or change window links. It omits `-r`: on tmux 3.7 and 3.7c, a read-only observer can make independent process-lane `send-keys` fail with "client is read-only". The private writer restricts observation commands without that flag. Closing an observer removes its attached client. Native policy such as `destroy-unattached` can then destroy its session. A startup check cannot guarantee lifetime preservation when options or other clients can change. The library does not rewrite borrowed options or retain hidden clients to prevent that policy. Housekeeping assumes the server's native command semantics. Aliases and hooks can change even builtin commands; control framing does not authenticate server output. This transport does not enable general command acceleration. Automatic reconnect and subscription replay are not implemented. Opening a new connection creates a distinct generation; callers must establish new watches and treat the interval between connections as a gap. --- # Options and hooks Source: https://libtmux.org/en/lua/latest/guides/settings/ > Source-owned Lua guide at 547c9c4228e0. Options and hooks return Requests through the process lane. Session, Window and Pane handles select their own storage scope. Server methods default to server options; use `scope = "global_session"` or `"global_window"` for global defaults. Server hook methods require one of those explicit global scopes. There is no global pane scope. A scope that conflicts with the handle or built-in definition fails before I/O. The [release catalog](../options/) records exact names, types and scopes for supported tmux releases. Names do not accept tmux's abbreviations. Unknown releases fail with `unsupported_version`. User options support names such as `@project_name`, with ASCII letters, digits, underscores, dots and hyphens after `@`; broader native names return `unsupported_name`. ## Read a setting `get_option(name, options)` returns a record with `present`, `inherited`, `type`, requested `scope` and `target`. Scalar `value` preserves booleans, integers and bytes. Choice values use their canonical names. Keys, colours, styles and command options return native canonical text. Reads never execute that text. `list_options(options)` returns records sorted by name. Reads include inherited values by default. Set `inherit = false` to inspect only the selected table. `present = false` distinguishes absence from an empty string, `false` or an explicit empty array. Local and inherited reads are separate observations; concurrent changes can occur between them. Arrays return ordered `entries`, each with a native zero-based `index` and `value`. Indices may have gaps. An `index` read option selects a slot after reading the complete array, preserving the distinction between an absent slot and an empty string. An absent slot has `present = false` and no entries. ## Change a setting `set_option(name, value, options)` accepts booleans for flags, integers within the release-specific range, exact choice names and NUL-free strings for string, key, colour and style options. Native key, colour and style grammar is still checked by tmux. Command values use the program record described below. Input structure, types, bounds and scopes are checked before I/O. Use `index` to write one array slot. To replace a sparse array, supply `{ entries = { { index = 0, value = "first" }, ... } }`. The library copies and validates the whole input, clears the local array, then writes slots in index order in one stop-on-error command group. Empty entries create an explicit empty local array. A first local indexed write also creates a local array; it does not copy inherited slots. Replacement is not atomic. Native value validation or hooks may fail after an earlier write; the returned error preserves the process result without claiming which slots completed. tmux can run hooks between group commands. `append = true` concatenates a scalar string or an indexed string-array value. Whole-array append is unsupported: native separator splitting cannot preserve arbitrary string elements. Indexed colour or command append is rejected because tmux replaces those values instead of appending. `unset_option(name, options)` removes a local override, restoring inheritance. For global defaults it restores tmux's default. An optional `index` removes one array slot. The API never uses tmux's wider `-U` operation, which can also remove pane overrides. ## Store and run hook programs `set_hook(name, program, options)` stores a tmux command program. Supply exactly one of `commands`, a dense sequence of argv sequences, or `source`, explicit tmux program text. `commands` encodes each argument as literal tmux-parser data in one command group. Commands retain their own native format and shell semantics. `source` is not pre-parsed; tmux aliases and grammar are resolved by tmux. Neither form is evaluated as Lua. Use `index` for a built-in hook slot. Without an index, setting a built-in hook replaces its array with one program. `append = true` adds a program at the first free native slot. Indexed append is rejected because native command-array append replaces that slot. Invalid native source can clear an existing array before tmux reports failure; storing a hook is not transactional. `get_hook` and `list_hooks` return the same presence and inheritance metadata as options. Built-in hooks have sparse `entries` containing `index` and canonical program `source`. Canonical text may expand aliases or reorder flags; it does not reconstruct the original argv. Custom `@` hooks return a single `source`. They can be retrieved by name but are omitted from `list_hooks`, since tmux cannot distinguish them from ordinary user options. `unset_hook` removes the selected hook or slot. Session, Window and Pane handles offer `run_hook(name, options)`. Execution uses that live context's native hook lookup. Only process limits are accepted; storage scopes, indices, append and replacement source do not select a hook for execution. Custom hook execution requires tmux 3.3 or newer. A successful process result means tmux completed its command; malformed custom source may still be ignored by tmux without an error. ## Bounds and failures Settings use at most one MiB of encoded input and aggregate native output. Listings accept at most 4096 rows. Array replacement accepts at most 512 entries and remains subject to the aggregate argv and byte limits. Programs accept at most 1024 commands, 4096 arguments and one MiB after encoding. `process` accepts deadline, timeout, output and cleanup limits. Caller-provided process environment, cwd and stdin are rejected. Output preserves bytes even when the caller uses a C locale. No operation changes the caller's environment. Canceling a client does not undo accepted tmux mutations. Errors distinguish `not_sent`, `unknown` and `completed` effects. A stale handle detected after a completed command retains that completed effect; it does not imply rollback. --- # Pane operations Source: https://libtmux.org/en/lua/latest/guides/panes/ > Source-owned Lua guide at 547c9c4228e0. Pane handles perform explicit asynchronous operations through the pinned PROCESS endpoint. Each method returns a Request; await it inside a managed runtime task or use its completion callback. Snapshot records remain plain captured data. Obtain a handle from a creation receipt or `server:handle`. ```lua local capture = assert(pane:capture({ history_lines = 20 }):await()) local text = assert(capture:text()) assert(pane:send_text("printf '%s\\n' ready"):await()) assert(pane:send_keys({ "Enter" }):await()) ``` The executable [integration fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/pane.lua) includes connection setup, creation, output barriers and teardown. ## Capture and text `capture()` returns `{ bytes, target }` with a pure `text()` method. `bytes` preserves tmux's stdout, including its terminal newline and invalid UTF-8. `text()` validates UTF-8 strictly and returns the same string; invalid input returns `nil, err` with `invalid_utf8`. It performs no replacement, trimming, newline conversion or tmux I/O. Ordinary capture reads rendered screen/history cells. It does not recover the original PTY byte stream, prove application completion, or establish an ordered handoff to observation. It does not enter, exit or navigate copy mode. The default captures the visible terminal screen. `history_lines = N` adds up to N history rows, bounded at 1,000,000. Alternatively, `start_line` and `end_line` accept integer row offsets or `"-"`: zero is the first visible row, negative offsets refer to history, `start_line = "-"` selects all retained history and `end_line = "-"` selects the visible screen's end. Explicit ranges cannot be combined with `history_lines`. tmux clamps ranges to available data. | Option | Native behavior | Availability | | --- | --- | --- | | `join_lines` | Join wrapped rows and preserve trailing spaces (`-J`). | 3.2a+ | | `preserve_spaces` | Preserve trailing spaces (`-N`). | 3.2a+ | | `escape_sequences` | Include text/background attribute sequences (`-e`). | 3.2a+ | | `escape_nonprintable` | Request native octal escaping (`-C`). | 3.2a+ | | `alternate_screen` | Select tmux's alternate grid (`-a`); missing grid errors. | 3.2a+ | | `trim_empty_cells` | Omit trailing empty cells (`-T`). | 3.4+ | | `mode_screen` | Capture the active mode screen when available (`-M`). | 3.6+ | | `ignore_missing_alternate` | With `alternate_screen`, return one newline if the grid is missing (`-q`). | 3.2a+ | | `pending_escape_sequences` | Capture incomplete input held by tmux's parser (`-P`). | 3.2a+ | | `hyperlinks_only` | List native hyperlink URLs instead of cell text (`-H`). | 3.7+ | | `line_numbers` | Prefix rows with offsets relative to the visible screen (`-L`). | 3.7+ | | `line_flags` | Prefix rows with native grid flags (`-F`). | 3.7+ | `alternate_screen` cannot be combined with history, explicit ranges or `mode_screen`. Unsupported version flags fail before dispatch. `pending_escape_sequences` selects parser input instead of screen cells. Only `escape_nonprintable` and process limits apply; other enabled capture options are rejected. For example, a pending ESC followed by `[` produces `"\027[\n"`, or `"\\033[\n"` with native octal escaping. The final newline belongs to tmux's print output, not the pending input. `hyperlinks_only` preserves tmux's URL listing, including native deduplication and spacing. It does not guarantee an exhaustive URL inventory: tmux limits the number of distinct links collected to the grid width. Screen/range selection, joined rows and line metadata still apply. Attribute sequences, octal escaping, preserved spaces and empty-cell trimming are rejected because tmux ignores them in this mode. No matches produce one newline. `line_numbers` and `line_flags` preserve native prefixes; with both enabled, the number precedes the flags. Flags include `D`, `H`, `O`, `P`, `W` and `X` for dead, hyperlink, output-start, prompt-start, wrapped and extended rows; `-` means none. The result remains bytes, not parsed row records. The executable [capture fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/capture.lua) demonstrates the supported capture modes with output barriers and cleanup. `capture_to_buffer(name, options)` writes directly to an explicit named buffer and returns `true` when the native command completes. It accepts the same capture options and [buffer creation names](../buffers/#storage-and-identity). Nonempty capture replaces the current slot. Empty capture leaves an existing buffer unchanged and does not create a missing buffer; this includes empty pending input and quiet missing alternate grids. Buffer capture stores native bytes without adding the print newline. A pending ESC followed by `[` is stored as `"\027["`, whereas `capture()` returns `"\027[\n"`. Another client may replace the buffer before a subsequent read. Process output limits bound client output, not storage inside the tmux daemon; use a capture range to limit the selected rows. ## Clear history `clear_history()` clears the pane's retained history and exits all its modes, including copy mode. It leaves visible screen cells intact. This is an explicit shared-state mutation; cancellation of an unrelated Request never calls it. `clear_history({ clear_hyperlinks = true })` also clears hyperlink storage, including links referenced by visible cells. This option requires tmux 3.4; older versions return `unsupported` before changing history or mode state. Success returns `true` under the same process, generation and error contracts as the other Pane mutations. ## Text, keys and copy mode `send_text(text)` sends bounded NUL-free UTF-8 with native `send-keys -l`. It appends no Enter. A CR or LF already present in the argument remains explicit caller input. It accepts at most 65,536 bytes; arbitrary binary input is not part of this method. `send_keys(names, options)` accepts a dense sequence of up to 1,024 names. Supported names include Enter, Escape, Tab, BTab, Space, BSpace, arrows, Home/End, Insert/Delete and their IC/DC aliases, PageUp/PageDown aliases, F1–F12 and numeric keypad names. C-, M- and S- modifiers may prefix these names or one printable ASCII character. Names are bounded at 64 bytes. `repeat_count` is an integer from 1 to 1,000. Typos return `invalid_key`; recognized deferred native categories such as mouse and user-defined keys return `unsupported`. Unmodified literal characters belong in `send_text`. Both methods preserve native mode and `synchronize-panes` behavior. Modes can intercept input; synchronization can copy it to sibling panes. Dead or input-disabled panes can accept a command without delivering input. Success means tmux processed the operation, not that an application consumed it. The library does not change these policies or infer shell-command success. `copy_mode({ page_up = true })` explicitly enters copy mode. `copy_command` sends one validated action through `send-keys -X`, with optional arguments and `repeat_count`. Entry, navigation and cancellation affect shared pane UI. No automatic cleanup exits a mode that another client may be using. The initial action subset includes cursor/word/paragraph/page/history navigation, selection marking, rectangle modes, refresh, search and jumps. For example: ```lua assert(pane:copy_mode():await()) assert(pane:copy_command("search-forward-text", { "ready" }):await()) assert(pane:copy_command("page-up", {}, { repeat_count = 2 }):await()) assert(pane:copy_command("cancel"):await()) ``` Unknown actions or incorrect argument counts return `invalid_copy_command`. Recognized deferred actions return `unsupported`, including copy/append, clipboard/pipe actions and newer navigation commands. This subset does not claim complete native copy-mode parity. Native command completion does not guarantee a search match or cursor movement. ## Resize, kill and respawn `resize({ width = N, height = N })` requests absolute dimensions; `resize({ direction = "left", amount = N })` adjusts one direction. Forms are mutually exclusive, dimensions/amount are 1–65,535 and adjustment defaults to one. Native layout constraints can clamp the result, resize neighbors and unzoom the window. Obtain a fresh snapshot when the resulting geometry matters. `kill()` targets only the handle's pane ID. Native removal of the last pane also destroys its window and can remove links or empty sessions elsewhere. This is an explicit mutation; canceling another Request never calls it. `respawn(options)` reuses the same pane identity. Without `kill = true`, an active pane produces a native error. Omitted `argv`/`shell` reuses its previous program; explicit launch options follow [creation](../creation/), including absolute `cwd`, environment and separate literal argv/shell forms. Respawn resets the terminal screen and mode. It can terminate the old program before a later spawn failure, and tmux success does not prove executable startup. These methods return `true` on successful native completion. They preserve typed errors and partial command output on failure, with no automatic retry. All options must be plain records. The nested `process` record accepts timeout, deadline, output limit and drain/kill timeouts as described in [commands](../commands/). Generation validation and runtime byte limits apply before dispatch; native completion still waits for client exit and both EOFs. ## Selection, titles and swaps `select()` changes the window's shared active pane. It unzooms when changing panes unless `keep_zoom = true`. This explicit mutation affects other clients and can run native focus and selection hooks. `set_title(text)` sends format-literal UTF-8: `#{pane_id}` stays text. NUL, ASCII control bytes and DEL are rejected before dispatch because native tmux can silently ignore them. The limit is 65,536 bytes. Exact tmux 3.7 also silently ignores empty titles, so that combination returns `unsupported`. Other accepted releases allow clearing the title. tmux's native name cleaning still applies; from 3.7, backslashes can be doubled. Completion does not promise byte-exact storage for every accepted title. `swap(other_pane, options)` swaps two explicit, different panes from the same Server. Their stable IDs follow them into their new windows. The default uses native `-d`: across windows, an active pane moved out is replaced at its old position. Within one window, an active source pane can remain selected after moving positions. Neither active identity nor active position is preserved in every case. `select = true` uses native selection of the swapped panes. `keep_zoom = true` preserves each window's zoom. Swaps change inherited window options and the pane relationships visible through every linked window. These methods return `true` on native completion and share the process limits above. Missing targets retain the native failure and its partial receipt. See [topology operations](../topology/) for Session and Window mutations. --- # Persistent environment Source: https://libtmux.org/en/lua/latest/guides/environment/ > Source-owned Lua guide at 547c9c4228e0. Server methods address tmux's global environment store. Session methods address that session's local store. Each method returns a Request; reads never evaluate shell text. Window and Pane handles reject these operations with `invalid_scope`. ```lua local adapter = require("libtmux.runtime.luv") local result, err = adapter.run(function(runtime) local server = assert(runtime:connect({ binary = "/usr/bin/tmux", socket_path = "/tmp/example-tmux.sock", config_path = "/dev/null", }):await()) local snapshot = assert(server:snapshot():await()) local session = assert(server:handle(snapshot, snapshot.sessions[1])) assert(server:set_environment("APP_MODE", "global"):await()) assert(session:set_environment("APP_MODE", "local"):await()) local local_value = assert(session:get_environment("APP_MODE"):await()) assert(session:unset_environment("APP_MODE"):await()) local inherited = assert(session:get_environment("APP_MODE", { inherit = true }):await()) return { local_value = local_value, inherited = inherited } end) assert(result, tostring(err)) ``` Names must match `[A-Za-z_][A-Za-z0-9_]*` and fit within 256 bytes. Native tmux accepts some other names; this API returns `unsupported_name` for them. Values are NUL-free byte strings up to one MiB. Empty strings, embedded newlines, quotes, dollar signs and invalid UTF-8 remain bytes without interpolation. ## Reads and storage source `get_environment(name, options)` returns one record. `list_environment(options)` returns records sorted by portable name. Records have these fields: | Field | Meaning | | --- | --- | | `name` | Portable name | | `state` | `value`, `removed`, or `absent`; lists omit absent names | | `value` | Exact bytes for `value`, including an empty string | | `hidden` | Native visibility flag; omitted when absent | | `scope` | Storage source: `global` or `session` | | `inherited` | Whether a session read used global fallback | | `target` | Session reference when the source is a session | Reads default to local storage, with `inherit = false`. A session read with `inherit = true` uses global storage only when the name is absent locally. Local hidden entries and removal markers suppress fallback. If both stores lack a named entry, the returned absent record describes the requested session. Named reads detect hidden entries automatically. Lists include hidden entries by default; `include_hidden = false` omits them. An inherited list still reads local hidden entries to prevent a hidden override from exposing a global value. Treat returned hidden values as sensitive application data. The optional `scope` must match the receiver: `global` for Server, `session` for Session. `inherit = true` requires a session read. `include_hidden` applies only to lists. Unsupported option combinations fail before I/O. ## Mutations and inheritance `set_environment(name, value, { hidden = true })` stores a hidden value. Omitting `hidden` stores an ordinary value and clears an existing hidden flag. `unset_environment(name)` deletes the stored entry, allowing global fallback for a session. `remove_environment(name)` installs a removal marker that blocks fallback and preserves the entry's existing native hidden flag. Each mutation returns `true` after native completion. These stores influence newly spawned processes. They do not change the environment of processes already running. A merged read is not an exact future process environment: tmux also supplies variables and may obtain PATH from the creating client. ## Consistency and bounds Reads use literal `show-environment -s` arguments and a bounded decoder, never a shell evaluator. The decoder recognizes the thirteen catalog releases from 3.2a through 3.7c. It reverses the 3.4–3.5a printer escapes and the additional 3.4 variable-like dollar escape. Unsupported releases fail explicitly. A listing of a nonportable removal name can resemble several portable removal rows. Before returning a list, the library verifies each parsed removal through named reads in the same scope and visibility, in groups of at most 128 commands. Missing, duplicate, extra or changed verification rows produce `inconsistent`. The verification uses aggregate group completion and makes no per-member effect claims. Nonportable assignments fail decoding. This verification does not make multiple reads a transaction: concurrent native changes can still occur between observations. There is no automatic retry. An operation reads at most one MiB of aggregate stdout and stderr and 4096 source entries, including both visibility views and global fallback. Verification output counts toward the byte limit. Runtime capacity accounts for copied input, raw output and decoded records through callback delivery. Capacity and protocol errors return no partial list. `process` accepts `timeout`, `deadline`, `max_output_bytes`, `drain_timeout` and `kill_timeout`; its output limit cannot exceed one MiB. A timeout applies to each native client; an absolute deadline also bounds later clients. Process stdin, environment and working-directory overrides are unavailable here. Closed handles and stale generations reject dispatch or publication. Once a client is sent, cancellation cannot undo accepted daemon work. Native errors retain their receipt; decoding or consistency failures after successful reads have `effect = "completed"`. --- # Query captured data Source: https://libtmux.org/en/lua/latest/guides/query/ > Source-owned Lua guide at 547c9c4228e0. `libtmux.query` filters ordinary Lua records without contacting tmux, starting a loop, or loading a codec. Supply a schema for an ordinary sequence, including an empty one. [The native query example](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/examples/native_query.lua) shows both structured criteria and a Lua predicate over the same records. `query.select(rows, schema)` copies the sequence and retains the record objects. The result is a dense one-based Lua table: indexing, `#` and `ipairs` behave normally. `:where(criteria)` and `:filter(predicate)` return new selections. They preserve input order, duplicate records and shared record references. The input and returned tables remain mutable; they are not live views. | Operation | Result | | --- | --- | | `:first()` | First record, or nil | | `:one()` | Record, or `nil, no_match/multiple_matches` error | | `:one_or_nil()` | Zero or one record; multiple matches return an error | | `:exists()` / `:count()` | Whether any records exist / sequence length | | `:iter()` | Fresh local iterator yielding records | | `:to_table()` | Shallow sequence copy without selection methods | Free functions accept the sequence first and its schema last. A selection carries its schema. `query.compile(schema, criteria)` returns a compiled handle or `nil, err`; `query.where(rows, compiled)` uses that handle's schema for ordinary rows. Compilation copies the schema and criteria. Mutating their original tables cannot change the compiled query. Convenience validation errors raise structured error tables; `compile` and cardinality errors return them. Trusted predicates run as ordinary Lua and their errors propagate unchanged. ## Criteria and projections Schema fields declare `string`, `number` or `boolean`, with optional `nullable` and `supported` booleans. Relations declare `cardinality = "one"` or `"many"` and a child `schema`. To-many arrays must describe the complete relationship. Scalar criteria are equality shorthand; `false` is a value. Operators are `eq`, `ne`, `one_of`, `none_of`, `lt`, `lte`, `gt`, `gte`, `contains`, `starts_with`, `ends_with` and `is_null`. Strings compare literal bytes. `AND`/`OR` contain dense arrays of criteria; `NOT` contains one criterion. Multiple fields and operators imply AND. To-many relations use `some`, `every` or `none`; to-one relations use `is` or `is_not` with criteria or `query.NULL`. `query.NULL` represents a loaded absent nullable value or to-one relation. A missing key is unloaded. Unloaded and unsupported data produce errors even inside branches that would otherwise short-circuit. The entire grammar is validated first, then every required projection, then matching begins. Invalid criteria fail even when the input is empty. Empty criteria and `AND` match; empty `OR` and `one_of` do not; empty `none_of` matches. On empty relationships, `some` is false and `every`/`none` are true. For an absent to-one relation, `is = criteria` is false, `is_not = criteria` is true, `is = NULL` is true, and `is_not = NULL` is false. Criteria reject metatables, functions, cycles and non-finite numbers. Limits are depth 32, 4,096 copied nodes, 1,024 members per membership operator, 65,536 aggregate string/key bytes and a conservative 524,288-byte encoding estimate. These limits bound criteria validation. Local row traversal and arbitrary caller predicates are synchronous CPU work. ## Versioned JSON criteria `query.encode_json(schema, criteria, codec)` returns a JSON string or `nil, err`. `query.decode_json(schema, text, codec)` returns new plain criteria tables or `nil, err`. Both validate the complete grammar against the supplied local schema. The wire does not supply its own schema or executable predicates. Pass the consumer's codec explicitly. The supported interface is lunajson 1.2.3's `encode(value, null)` and `newparser(text, callbacks)` SAX API; an ordinary JSON `decode` function cannot preserve evidence of duplicate keys. Core imports and ordinary queries require only Lua. Codec functions are trusted synchronous code; the library does not load a codec automatically. ```lua local query = require("libtmux.query") local json = require("lunajson") local schema = { fields = { active = { type = "boolean" } } } local text = assert(query.encode_json(schema, { active = false, AND = {} }, json)) local criteria = assert(query.decode_json(schema, text, json)) ``` This Lua API's versioned profile has exactly two envelope members: ```json {"version":"libtmux.where/v1","where":{"active":false,"AND":[]}} ``` Profile compatibility is scoped to this Lua API; cross-port conformance has not been established. Wire operators retain their Lua spelling, including uppercase `AND`, `OR` and `NOT`. Unknown members and versions are rejected. Schema positions determine array versus object encoding: empty `AND`, `OR`, `one_of` and `none_of` use `[]`; empty criteria use `{}`. Decode rejects a container of the wrong kind, including an empty object in an array position. `query.NULL` becomes JSON null and decodes back to the same sentinel. False remains false. Encoding marks arrays only in private copies and leaves caller criteria and schemas unchanged. The decoder rejects duplicate decoded keys, trailing non-whitespace, malformed Unicode, invalid UTF-8 and non-finite numbers. Wire numbers have magnitude at most 9,007,199,254,740,991; nonzero number tokens that underflow to zero are rejected. Numbers otherwise use the host's floating-point representation. Strings containing arbitrary non-UTF-8 bytes remain usable in local criteria and require a separate binary representation at a consumer boundary. Input is capped at 524,288 bytes before constructing the SAX parser. Parsing checks depth 32, 4,096 nodes and 65,536 aggregate decoded string/key bytes as events arrive. Membership remains capped at 1,024 values. Encoding applies the same wire budgets and output-byte cap. Envelope members and keys count toward wire limits, so criteria at a local limit may exceed a wire limit. Errors retain `code`, `operation`, `message` and `path`. `invalid_json` covers syntax and scalar encoding failures, `invalid_wire` covers envelope/container shape, and `unsupported_wire_version` rejects another profile version. `invalid_codec` and `codec_error` report absent or failing injected codecs. Existing grammar errors and `query_limit` remain structured errors as well. ## Query live state explicitly `server:query_panes(options)` returns a `Request>`. `server:query(options)` accepts `kind` for session, window, pane, window_link, client or buffer records. Both return `rows`, a canonical Selection, plus the captured `snapshot`, executed `plan`, acquisition interval, `complete` flag and detected `races`. Collection methods on these results remain local. Pass structured `where` criteria and choose a `pushdown` mode: | Mode | Behavior | | --- | --- | | `never` | Capture the graph and evaluate all criteria locally. | | `auto` | Also apply supported necessary pane predicates at the source. | | `require` | Reject an incomplete native translation before any listing. | Native translation currently supports bounded equality tests on selected pane IDs, booleans and integer fields. Other criteria remain local. AND can supply necessary source predicates; partial OR, NOT and relationship predicates cannot. All entity kinds support local evaluation. `require` for another entity kind reports `unsupported_pushdown`. `server:explain_panes(options)` and `server:explain(options)` return Requests with the ordered command phases, projections, relation hydration paths, pushed predicates and residual reasons. Explaining performs no tmux I/O. Inputs and projections validate before any live dispatch and are copied so later caller mutation cannot change the query. The `snapshot` option accepts [snapshot acquisition options](../snapshots/). Required criterion fields are added to explicit projections. The current implementation captures the whole relationship graph before an optional native candidate listing. It preserves canonical order and linked-window context; filtering candidate IDs never removes children from quantified relationships. This establishes semantics, not a performance advantage. The graph and candidate listing cover different moments. `candidate_missing` reports a candidate absent from the captured graph; `candidate_changed` reports disagreement with a pushed predicate. `complete=true` means no known inconsistency, not an atomic view. `snapshot.strict` adds its one topology verification pass; it cannot freeze state across the later candidate listing. The acquisition interval covers both phases. Snapshot and candidate listing each apply the requested row/byte limits; retained data also shares the runtime byte budget. Expert `native_filter` accepts an explicit pane format, up to 16 KiB. It is mutually exclusive with `where` and `pushdown`. It uses tmux's format language as supplied, has no equivalent local predicate, and receives no portability guarantee. Use structured criteria for untrusted data. The [public live-query fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/live_query.lua) exercises linked-window duplicates, projected fields, relationship quantifiers and an expert filter through both adapters. --- # Read and paste named buffers Source: https://libtmux.org/en/lua/latest/guides/buffers/ > Source-owned Lua guide at 547c9c4228e0. Buffers hold bytes in the tmux daemon. Use an explicit name for every read, write, deletion and paste; these methods never infer the most recent buffer. Each live operation returns a Request through the PROCESS endpoint. ```lua assert(server:set_buffer("build-output", "one\000two\n"):await()) local content = assert(server:show_buffer("build-output"):await()) assert(content.bytes == "one\000two\n") assert(pane:paste_buffer("build-output", { bytes = "raw", linefeed_separator = true, }):await()) ``` The executable [integration fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/buffer.lua) includes connection setup, owned pane readers, output barriers and teardown. ## Storage and identity `server:set_buffer(name, bytes)` replaces the current value at that name. Input accepts one byte through one MiB, including NUL, invalid UTF-8 and trailing newlines. It uses native stdin loading, so no shell or argv decoder processes the value. Empty input is rejected with `invalid_argument`: tmux would accept it without creating or clearing a buffer. Delete explicitly to remove the value. New names must be nonempty UTF-8, at most 4,096 bytes, without NUL, ASCII controls, DEL or backslash. The excluded creation forms return `unsupported_name`; some tmux releases accept them, while newer name cleaning can change their stored key. Further native name validation still applies. Spaces, leading dashes and `#{...}` are literal. Buffer names do not undergo tmux format expansion. Use [`pane:capture_to_buffer(name, options)`](../panes/#capture-and-text) to store a native pane capture directly. It uses these creation-name rules; empty capture leaves the named slot unchanged. Native buffer capture does not add the newline used by capture's printed output. `server:show_buffer(name)` returns `{ name, bytes }` with a pure `text()` method. `bytes` preserves exact stdout. `text()` requires valid UTF-8 and performs no trimming, replacement or newline normalization. Invalid UTF-8 returns `nil, err` with `invalid_utf8`. Mutating this returned record changes neither the daemon's value nor future Requests. `server:delete_buffer(name)` removes the current value on tmux 3.4 and later. It returns `unsupported` before dispatch on 3.2a, 3.3 and 3.3a: those releases can delete the most recent buffer when the requested name is missing. Checking existence first would still race with other clients. This limitation does not affect `paste_buffer` with `delete_after`, whose native lookup rejects missing names before deletion. Read, delete and paste accept broader exact observed names: nonempty NUL-free strings up to 4,096 bytes. On supported operations, a missing buffer retains tmux's error and native receipt. A name identifies a current slot, not a persistent buffer incarnation. Another client can replace its value after a snapshot or read. These methods operate on the value present when tmux executes them; no preflight or transaction claim hides that possibility. ## Paste behavior `pane:paste_buffer(name, options)` targets the handle's exact pane ID and adds no Enter. Completion means the native command finished; it does not prove an application received or consumed the bytes. | Option | Behavior | | --- | --- | | `bytes = "native"` | Default: preserve the connected daemon's native policy. tmux 3.7+ sanitizes control and invalid UTF-8 bytes; earlier releases paste raw bytes. | | `bytes = "raw"` | Disable that sanitization with `-S` on 3.7+; earlier releases already behave this way. Newline conversion remains separately controlled. | | `linefeed_separator = true` | Preserve LF. By default, tmux changes each LF to CR. | | `separator = text` | Replace each LF with this bounded NUL-free string, including empty. Excludes `linefeed_separator`, even explicit false. | | `bracket = true` | Add native bracketed-paste wrappers only when the pane has enabled that terminal mode. | | `delete_after = true` | Remove the named buffer after native paste processing. | Input-disabled panes can accept paste without receiving bytes, including a successful `delete_after`. Paste does not use the send-keys dispatcher; do not assume its copy-mode or synchronized-pane routing. Native aliases and hooks remain observable as described in [command execution](../commands/). ## Bounds and effects Options must be plain records and are copied before dispatch. The nested `process` options accept timeout, deadline, output limit and drain/kill limits from [Pane operations](../panes/). Buffer output defaults to one MiB and cannot be raised above that bound. Overflow returns an error with available partial output, never a successful truncated BufferValue. Runtime input and output reservations remain charged through delivery. Closed or stale generations reject new work; continuity lost after native success preserves `effect = "completed"` and the receipt. Cancellation after dispatch can have an unknown effect and never retries the mutation or kills the target pane. Binary append, buffer renaming and explicit file load/save remain pending typed APIs. No read-concatenate-write operation is presented as atomic. --- # Switch and detach clients Source: https://libtmux.org/en/lua/latest/guides/clients/ > Source-owned Lua guide at 547c9c4228e0. Use an explicit client name and TTY from a snapshot. A selector addresses the current attachment at that name, including a replacement that attached after the snapshot. Client records do not prove attachment continuity. Inside a [runtime task](../runtime/), with a connected `server`, choose the client and destination session by their observed names: ```lua local snapshot = assert(server:snapshot():await()) local observed = snapshot.clients:where({ name = "/dev/pts/7" }):one() local record = snapshot.sessions:where({ name = "work" }):one() local destination = assert(server:handle(snapshot, record)) local selector = { name = observed.name, tty = observed.tty } assert(server:switch_client(selector, destination):await()) ``` `switch_client` requires a Session handle from the same Server. Its private session ID determines the destination; changing a returned reference cannot redirect the request. By default, switching preserves the destination's environment. Set `{ update_environment = true }` to apply the client's native `update-environment` values. Switching still triggers tmux's attachment, selection, sizing, focus and lifecycle effects. `server:detach_client(selector)` detaches that current client. It neither detaches all clients nor requests a parent-process signal or shell command. Success means the native detach command completed; it does not mean the borrowed terminal process was reaped. Closing the library connection leaves other attached clients running. Both methods return Requests that resolve to `true` on native completion. The [owned-terminal fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/test_client.py) runs these [public API operations](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/client.lua) through standalone Lua and Neovim, including same-process reconnection and environment updates. ## Selection and failure The selector must be a plain record containing only `name` and `tty`. Both are exact NUL-free byte strings of at most 4,096 bytes. `name` must be nonempty; `tty` may be empty for a client without a terminal. No implicit current client or abbreviated name is selected. A fresh listing checks the complete name/TTY pair and native lookup aliases. Missing pairs return `missing_target`; multiple native matches return `ambiguous_target`. Neither dispatches a mutation. The listing and mutation are separate commands: another attachment can replace the selected client between them. No PID or timestamp check can prove continuity across native detach/exec/reconnect. Use these methods only when addressing the current attachment is the intended operation. Selectors and options are copied before I/O. Closed handles and stale daemon generations reject work. A failure before mutation dispatch has `effect = "not_sent"`; cancellation after dispatch may have an unknown effect. Continuity loss after success retains the native receipt and `effect = "completed"`. Mutations are never retried automatically. Native aliases and hooks follow the [command execution contract](../commands/). The `process` options accept `timeout`, `deadline`, `max_output_bytes`, `drain_timeout` and `kill_timeout`. The output cap defaults to one MiB and cannot exceed it. Each subprocess has its own timeout; an absolute deadline also bounds later subprocesses. Preflight accepts at most 1,024 client rows. Malformed or oversized listings fail without mutating a target. Runtime byte capacity covers input, listing data and native receipts through delivery. ## Interactive attachment `session:attach()` returns `unsupported_tty` before spawning: the current luv and Neovim adapters do not own an interactive terminal. It does not borrow the editor's terminal or turn a control observation into an interactive attachment. Interactive terminal ownership, client navigation, key tables and read-only toggles remain pending capabilities. --- # tmux field catalog Source: https://libtmux.org/en/lua/latest/guides/fields/ > Source-owned Lua guide at 547c9c4228e0. This curated catalog describes the scalar fields needed by the core entity model. It does not enumerate every tmux format. Edit [the source catalog](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/data/tmux-fields.json) and regenerate this reference together with Lua metadata and LuaLS field annotations: ```console $ python scripts/generate_fields.py ``` Check generated files without changing them: ```console $ python scripts/generate_fields.py --check ``` Generation uses Python and the pinned StyLua formatter; it needs no network or tmux process. The optional `--verify-source` argument accepts a local tmux Git checkout and verifies every pinned format mapping and source anchor. ## Availability and values `Since` means the first release supported by this catalog for that field, not necessarily the release that introduced it. The floor is tmux 3.2a. Source inspection establishes format availability; it does not establish complete runtime or platform compatibility. Later release strings retain known fields; development and prerelease strings require explicit capability evidence and are rejected by the schema helper. Record names are Lua aliases for literal tmux format names. IDs retain their `$`, `@`, and `%` prefixes. `number` fields represent integers; consumers must reject values outside the exact integer range of their Lua runtime rather than silently round them. All generated LuaLS fields are optional because a projection may leave a field unloaded. Nullable fields may be absent within an otherwise valid native context. Loaded absence uses `query.NULL`; an omitted key means not loaded. Known fields unavailable at the requested version remain in the query schema with `supported = false`. Empty text remains text for nonnullable string fields. The scalar catalog does not build relationships or perform I/O. Window index, active state, and flags belong to `window_link`. Windows and panes have no scalar session ID because a window can be linked into several sessions. `client_session` provides a session name, not a session ID. Client names and buffer names need contextual revalidation before later mutations. ## Source provenance - floor: [3.2a](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/format.c). - added: [3.3](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/format.c). - stable: [3.7c](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c). - upstream: [inspected upstream revision](https://github.com/tmux/tmux/blob/e880cf63e0a9fe095d7c5d313761520fb1a8653c/format.c). ## Server | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `pid` | [`pid`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L541) | number | no | 3.2a | server | | `socket_path` | [`socket_path`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2690) | string | no | 3.2a | server | | `version` | [`version`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2697) | string | no | 3.2a | server | | `start_time` | [`start_time`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3107) | number | no | 3.2a | server | - `pid`: Daemon process ID. - `socket_path`: Socket path reported by the daemon. - `version`: Daemon version string. - `start_time`: Daemon start time in Unix seconds. ## Session | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `id` | [`session_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2630) | string | no | 3.2a | session | | `name` | [`session_name`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2663) | string | no | 3.2a | session | | `created` | [`session_created`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3089) | number | no | 3.2a | session | | `activity` | [`session_activity`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3080) | number | no | 3.2a | session | | `attached` | [`session_attached`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2553) | number | no | 3.2a | session | | `window_count` | [`session_windows`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2681) | number | no | 3.2a | session | | `group` | [`session_group`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2571) | string | yes | 3.2a | session | | `grouped` | [`session_grouped`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2618) | boolean | no | 3.2a | session | | `path` | [`session_path`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2672) | string | no | 3.2a | session | - `id`: Session ID, including its dollar-sign prefix. - `name`: Session name. - `created`: Creation time in Unix seconds. - `activity`: Last activity time in Unix seconds. - `attached`: Attached client count, not a boolean. - `window_count`: Number of window links in this session. - `group`: Session group name; absent for an ungrouped session. - `grouped`: Whether this session belongs to a group. - `path`: Session working directory. ## Window | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `id` | [`window_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2844) | string | no | 3.2a | window | | `name` | [`window_name`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2936) | string | no | 3.2a | window | | `width` | [`window_width`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3015) | number | no | 3.2a | window | | `height` | [`window_height`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2835) | number | no | 3.2a | window | | `pane_count` | [`window_panes`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2973) | number | no | 3.2a | window | | `layout` | [`window_layout`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L839) | string | yes | 3.2a | window | | `visible_layout` | [`window_visible_layout`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L853) | string | yes | 3.2a | window | | `zoomed` | [`window_zoomed_flag`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3024) | boolean | no | 3.2a | window | - `id`: Window ID, including its at-sign prefix. - `name`: Window name. - `width`: Width in character cells. - `height`: Height in character cells. - `pane_count`: Number of panes owned by this window. - `layout`: Layout including panes hidden by zoom; absent without a layout tree. - `visible_layout`: Visible layout; absent without a layout tree. - `zoomed`: Whether this window is zoomed. ## Window link | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `session_id` | [`session_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2630) | string | no | 3.2a | session | | `window_id` | [`window_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2844) | string | no | 3.2a | window | | `index` | [`window_index`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2853) | number | no | 3.2a | winlink | | `active` | [`window_active`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2737) | boolean | no | 3.2a | winlink | | `flags` | [`window_flags`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2817) | string | no | 3.2a | winlink | - `session_id`: Session owning this contextual link. - `window_id`: Underlying window shared by links. - `index`: tmux index within the session, not Lua array position. - `active`: Whether this link is the session current window. - `flags`: Printable flags for this session/window link. ## Pane | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `id` | [`pane_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2152) | string | no | 3.2a | pane | | `window_id` | [`window_id`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2844) | string | no | 3.2a | window | | `index` | [`pane_index`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2161) | number | no | 3.2a | pane | | `active` | [`pane_active`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2028) | boolean | no | 3.2a | pane | | `title` | [`pane_title`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2384) | string | no | 3.2a | pane | | `width` | [`pane_width`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2411) | number | no | 3.2a | pane | | `height` | [`pane_height`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2143) | number | no | 3.2a | pane | | `left` | [`pane_left`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2225) | number | no | 3.2a | pane | | `top` | [`pane_top`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2393) | number | no | 3.2a | pane | | `pid` | [`pane_pid`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2285) | number | no | 3.2a | pane | | `tty` | [`pane_tty`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2402) | string | no | 3.2a | pane | | `current_path` | [`pane_current_path`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L915) | string | yes | 3.2a | pane | | `current_command` | [`pane_current_command`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L891) | string | yes | 3.2a | pane | | `start_command` | [`pane_start_command`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L865) | string | no | 3.2a | pane | | `dead` | [`pane_dead`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2075) | boolean | no | 3.2a | pane | | `dead_status` | [`pane_dead_status`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2106) | number | yes | 3.2a | pane | | `dead_signal` | [`pane_dead_signal`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2089) | string | yes | 3.3 | pane | | `dead_time` | [`pane_dead_time`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2120) | number | yes | 3.3 | pane | | `mode` | [`pane_mode`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2258) | string | yes | 3.2a | pane | | `mode_count` | [`pane_in_mode`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1138) | number | no | 3.2a | pane | | `synchronized` | [`pane_synchronized`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L2372) | boolean | no | 3.2a | pane | - `id`: Pane ID, including its percent-sign prefix. - `window_id`: ID of the window that owns this pane. - `index`: tmux pane index within its window. - `active`: Whether this pane is active in its window. - `title`: Pane title; an empty title remains an empty string. - `width`: Width in character cells. - `height`: Height in character cells. - `left`: Left cell offset in the window. - `top`: Top cell offset in the window. - `pid`: PID recorded for the pane process. - `tty`: Pseudo-terminal name; empty is preserved. - `current_path`: Process working directory when the OS can determine it. - `current_command`: Displayed command name; absent without pane shell context. - `start_command`: Stringified startup argv; this is not a shell-safe command. - `dead`: Whether tmux has a ready dead-process status. - `dead_status`: Exit status only when the process exited normally. - `dead_signal`: Signal name only when the process terminated by signal. - `dead_time`: Dead-pane display time in Unix seconds, when available. - `mode`: Top mode name; absent outside pane modes. - `mode_count`: Number of active modes, not a boolean. - `synchronized`: Effective synchronize-panes option. ## Client | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `name` | [`client_name`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1539) | string | no | 3.2a | client | | `tty` | [`client_tty`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1623) | string | no | 3.2a | client | | `pid` | [`client_pid`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1548) | number | no | 3.2a | client | | `session_name` | [`client_session`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1584) | string | yes | 3.2a | client | | `width` | [`client_width`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1677) | number | yes | 3.2a | client | | `height` | [`client_height`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1510) | number | yes | 3.2a | client | | `control_mode` | [`client_control_mode`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1480) | boolean | no | 3.2a | client | | `readonly` | [`client_readonly`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1572) | boolean | no | 3.2a | client | | `created` | [`client_created`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3071) | number | no | 3.2a | client | | `activity` | [`client_activity`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3062) | number | no | 3.2a | client | - `name`: Observed client name; not a persistent identifier. - `tty`: Observed terminal name; empty is preserved. - `pid`: Client process ID. - `session_name`: Attached session name, not a session ID. - `width`: Terminal width; nullable without a started TTY. - `height`: Terminal height; nullable without a started TTY. - `control_mode`: Whether this is a control-mode client. - `readonly`: Whether this client is read-only. - `created`: Creation time in Unix seconds. - `activity`: Last activity time in Unix seconds. ## Buffer | Record field | tmux format | Type | Nullable | Since | Native scope | | --- | --- | --- | --- | --- | --- | | `name` | [`buffer_name`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1416) | string | no | 3.2a | buffer | | `size` | [`buffer_size`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1449) | number | no | 3.2a | buffer | | `sample` | [`buffer_sample`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L1425) | string | no | 3.2a | buffer | | `created` | [`buffer_created`](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/format.c#L3048) | number | no | 3.2a | buffer | - `name`: Buffer name; revalidate before later mutations. - `size`: Buffer size in bytes. - `sample`: tmux printable preview; not complete buffer bytes. - `created`: Creation time in Unix seconds. --- # tmux option and hook reference Source: https://libtmux.org/en/lua/latest/guides/options/ > Source-owned Lua guide at 547c9c4228e0. This generated catalog records every canonical built-in option and hook in the 13 released tmux versions listed below. It describes native types and storage scopes; it does not establish runtime or platform compatibility. Edit [the catalog](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/data/tmux-options.json), then regenerate: ```console $ python scripts/generate_options.py ``` Check generated Lua metadata and this reference without changing files: ```console $ python scripts/generate_options.py --check ``` Both commands run offline with Python and the pinned StyLua formatter. `--verify-source` additionally accepts a local tmux Git checkout and checks every release tag, pinned source digest, definition and line anchor. It does not download sources, build tmux or start a server. ## Values and scopes Built-in names select their native storage scope; command flags alone do not enforce the caller's intended scope. Session and window defaults are separate global stores. Pane-capable options also support window storage; there is no global pane store. Unknown release strings require new source evidence: the private catalog does not fall back to the latest version. Flags use booleans, numbers use exact bounded integers, and choices use literal strings, including numeric-looking choices such as `"24"`. Keys, colours, styles and commands retain their native string grammars. A command value is tmux command-list source, not shell argv. Native grammar validation and remote conditions such as shell suitability still require tmux. Arrays retain native zero-based sparse indices and their element type. The separator is a set of splitting characters, not a reversible codec. An omitted array separator means space/comma; an empty separator preserves one complete command-list entry. Indexed assignment avoids splitting. All built-in hooks are command arrays; a scalar command option is not a hook. User options beginning with `@` are separate string scalars. Global built-in unset restores the default; local unset removes an override. Native aliases, prefix matching, default values and descriptive option text are outside this catalog. Source integer limits assume the supported target ABIs' 32-bit `int` and 16-bit `short`; the largest bound is 4294967295. ## Source releases | Release | Built-ins, including hooks | Hooks | | --- | ---: | ---: | | [3.2a](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c) | 165 | 61 | | [3.3](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c) | 181 | 65 | | [3.3a](https://github.com/tmux/tmux/blob/0b355ae8114511e1ff6359272b164f1cdf718e80/options-table.c) | 181 | 65 | | [3.4](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c) | 186 | 65 | | [3.5](https://github.com/tmux/tmux/blob/ac44566c9c7e3e94d23be6def4c7ae83472543f5/options-table.c) | 190 | 66 | | [3.5a](https://github.com/tmux/tmux/blob/549c35b06165f6ae023115eb76f83f2cbf945395/options-table.c) | 190 | 66 | | [3.6](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c) | 210 | 68 | | [3.6a](https://github.com/tmux/tmux/blob/cc117b5048f77a4842820f8ebbe3a86e5c077224/options-table.c) | 210 | 68 | | [3.6b](https://github.com/tmux/tmux/blob/0623d1e968423ad0c192e0d8debf1258671063d5/options-table.c) | 210 | 68 | | [3.7](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c) | 221 | 68 | | [3.7a](https://github.com/tmux/tmux/blob/0e418b62d259ce8da8970f75732cc6632ee4c3a0/options-table.c) | 221 | 68 | | [3.7b](https://github.com/tmux/tmux/blob/e802909de06012a4df6209d55e86487c56223163/options-table.c) | 221 | 68 | | [3.7c](https://github.com/tmux/tmux/blob/e476c1230b958df0cb12977517d24b3dc931375b/options-table.c) | 221 | 68 | Full commit identities, hashes for `options-table.c`, `options.c` and `tmux.h`, and per-entry source anchors are recorded in the source catalog. Repeated release labels below mean the extracted metadata is identical, not that defaults or other native behavior are identical. ## Options | Name | Releases | Scope | Value | Array separator | | --- | --- | --- | --- | --- | | [`activity-action`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L346) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `none`, `any`, `current`, `other` | — | | [`aggressive-resize`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L753) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | | [`allow-passthrough`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L804) | 3.3, 3.3a | window, pane | flag | — | | [`allow-passthrough`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L859) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | `off`, `on`, `all` | — | | [`allow-rename`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L763) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | flag | — | | [`allow-set-title`](https://github.com/tmux/tmux/blob/ac44566c9c7e3e94d23be6def4c7ae83472543f5/options-table.c#L900) | 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | flag | — | | [`alternate-screen`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L771) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | flag | — | | [`assume-paste-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L354) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`automatic-rename`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L779) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | | [`automatic-rename-format`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L786) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`backspace`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L193) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | key | — | | [`base-index`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L365) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`bell-action`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L374) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `none`, `any`, `current`, `other` | — | | [`buffer-limit`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L200) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 1..2147483647 | — | | [`clock-mode-colour`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L794) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | colour | — | | [`clock-mode-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L801) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a | window | `12`, `24` | — | | [`clock-mode-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1093) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `12`, `24`, `12-with-seconds`, `24-with-seconds` | — | | [`codepoint-widths`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L306) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string; sparse array | `","` | | [`command-alias`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L210) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string; sparse array | `","` | | [`copy-command`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L225) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string | — | | [`copy-mode-current-line-number-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1208) | 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-current-match-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L818) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-line-number-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1217) | 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-line-numbers`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1226) | 3.7, 3.7a, 3.7b, 3.7c | window | `off`, `default`, `absolute`, `relative`, `hybrid` | — | | [`copy-mode-mark-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L827) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-match-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L809) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-position-format`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1128) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | string | — | | [`copy-mode-position-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1140) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`copy-mode-selection-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1149) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`cursor-colour`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L245) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | colour | — | | [`cursor-style`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L252) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | `default`, `blinking-block`, `block`, `blinking-underline`, `underline`, `blinking-bar`, `bar` | — | | [`default-client-command`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L338) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | command | — | | [`default-command`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L382) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`default-shell`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L390) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`default-size`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L397) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`default-terminal`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L233) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string | — | | [`destroy-unattached`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L405) | 3.2a, 3.3, 3.3a | session | flag | — | | [`destroy-unattached`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L488) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `keep-last`, `keep-group` | — | | [`detach-on-destroy`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L413) | 3.2a, 3.3, 3.3a | session | `off`, `on`, `no-detached` | — | | [`detach-on-destroy`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L497) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `no-detached`, `previous`, `next` | — | | [`display-panes-active-colour`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L422) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | colour | — | | [`display-panes-colour`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L429) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | colour | — | | [`display-panes-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L436) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 1..2147483647 | — | | [`display-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L446) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`editor`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L240) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string | — | | [`escape-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L247) | 3.2a | server | 0..2147483647 | — | | [`escape-time`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L274) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 0..2147483647 | — | | [`exit-empty`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L256) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | flag | — | | [`exit-unattached`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L263) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | flag | — | | [`extended-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L271) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | `off`, `on`, `always` | — | | [`extended-keys-format`](https://github.com/tmux/tmux/blob/ac44566c9c7e3e94d23be6def4c7ae83472543f5/options-table.c#L320) | 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | `csi-u`, `xterm` | — | | [`fill-character`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L885) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`focus-events`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L280) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | flag | — | | [`focus-follows-mouse`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L670) | 3.7, 3.7a, 3.7b, 3.7c | session | flag | — | | [`get-clipboard`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L414) | 3.7, 3.7a, 3.7b, 3.7c | server | `off`, `buffer`, `request`, `both` | — | | [`history-file`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L287) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string | — | | [`history-limit`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L456) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`initial-repeat-time`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L664) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2000000 | — | | [`input-buffer-size`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L416) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 1048576..4294967295 | — | | [`key-table`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L468) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`lock-after-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L476) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`lock-command`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L486) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`main-pane-height`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L836) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`main-pane-width`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L844) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`menu-border-lines`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L359) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `single`, `double`, `heavy`, `simple`, `rounded`, `padded`, `none` | — | | [`menu-border-style`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L350) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`menu-selected-style`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L341) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`menu-style`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L332) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`message-command-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L493) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | style string | — | | [`message-format`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L736) | 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`message-limit`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L295) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 0..2147483647 | — | | [`message-line`](https://github.com/tmux/tmux/blob/9ae69c3795ab5ef6b4d760f6398cd9281151f632/options-table.c#L587) | 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `0`, `1`, `2`, `3`, `4` | — | | [`message-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L503) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | style string | — | | [`mode-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L852) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `emacs`, `vi` | — | | [`mode-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L860) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`monitor-activity`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L869) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | | [`monitor-bell`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L876) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | | [`monitor-silence`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L883) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | 0..2147483647 | — | | [`mouse`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L512) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | flag | — | | [`other-pane-height`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L894) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`other-pane-width`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L902) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`pane-active-border-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L910) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b | window | style string | — | | [`pane-active-border-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1315) | 3.7, 3.7a, 3.7b, 3.7c | window, pane | style string | — | | [`pane-base-index`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L919) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | 0..65535 | — | | [`pane-border-format`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L928) | 3.2a | window | string | — | | [`pane-border-format`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L984) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | string | — | | [`pane-border-indicators`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L992) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `off`, `colour`, `arrows`, `both` | — | | [`pane-border-lines`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L936) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a | window | `single`, `double`, `heavy`, `simple`, `number` | — | | [`pane-border-lines`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1274) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `single`, `double`, `heavy`, `simple`, `number`, `spaces` | — | | [`pane-border-status`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L944) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `off`, `top`, `bottom` | — | | [`pane-border-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L952) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b | window | style string | — | | [`pane-border-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1374) | 3.7, 3.7a, 3.7b, 3.7c | window, pane | style string | — | | [`pane-colours`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1027) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | colour; sparse array | `" ,"` | | [`pane-scrollbars`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1308) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `off`, `modal`, `on` | — | | [`pane-scrollbars-position`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1325) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `right`, `left` | — | | [`pane-scrollbars-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1316) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | style string | — | | [`pane-status-current-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L924) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`pane-status-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L933) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`popup-border-lines`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1053) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `single`, `double`, `heavy`, `simple`, `rounded`, `padded`, `none` | — | | [`popup-border-style`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1044) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`popup-style`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1035) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`prefix`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L521) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | key | — | | [`prefix-timeout`](https://github.com/tmux/tmux/blob/ac44566c9c7e3e94d23be6def4c7ae83472543f5/options-table.c#L388) | 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 0..2147483647 | — | | [`prefix2`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L528) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | key | — | | [`prompt-command-cursor-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L997) | 3.7, 3.7a, 3.7b, 3.7c | session | `default`, `blinking-block`, `block`, `blinking-underline`, `underline`, `blinking-bar`, `bar` | — | | [`prompt-cursor-colour`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L943) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | colour | — | | [`prompt-cursor-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L950) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `default`, `blinking-block`, `block`, `blinking-underline`, `underline`, `blinking-bar`, `bar` | — | | [`prompt-history-limit`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L332) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | 0..2147483647 | — | | [`remain-on-exit`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L961) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b | window, pane | `off`, `on`, `failed` | — | | [`remain-on-exit`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1443) | 3.7, 3.7a, 3.7b, 3.7c | window, pane | `off`, `on`, `failed`, `key` | — | | [`remain-on-exit-format`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1071) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | string | — | | [`renumber-windows`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L535) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | flag | — | | [`repeat-time`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L543) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a | session | 0..32767 | — | | [`repeat-time`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L759) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2000000 | — | | [`scroll-on-clear`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1084) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | flag | — | | [`session-status-current-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L958) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`session-status-style`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L967) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`set-clipboard`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L304) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | `off`, `external`, `on` | — | | [`set-titles`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L554) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | flag | — | | [`set-titles-string`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L561) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`silence-action`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L568) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `none`, `any`, `current`, `other` | — | | [`status`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L576) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `2`, `3`, `4`, `5` | — | | [`status-bg`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L584) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | colour | — | | [`status-fg`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L592) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | colour | — | | [`status-format`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L600) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string; sparse array | `" ,"` | | [`status-interval`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L612) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..2147483647 | — | | [`status-justify`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L622) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `left`, `centre`, `right`, `absolute-centre` | — | | [`status-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L630) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `emacs`, `vi` | — | | [`status-left`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L638) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`status-left-length`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L645) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..32767 | — | | [`status-left-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L654) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | style string | — | | [`status-position`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L663) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `top`, `bottom` | — | | [`status-right`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L671) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`status-right-length`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L681) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | 0..32767 | — | | [`status-right-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L690) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | style string | — | | [`status-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L699) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | style string | — | | [`synchronize-panes`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L970) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | flag | — | | [`terminal-features`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L323) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string; sparse array | `","` | | [`terminal-overrides`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L314) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string; sparse array | `","` | | [`tiled-layout-max-columns`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1397) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | 0..65535 | — | | [`tree-mode-preview-format`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1491) | 3.7, 3.7a, 3.7b, 3.7c | window, pane | string | — | | [`tree-mode-preview-style`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1500) | 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`update-environment`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L708) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string; sparse array | `" ,"` | | [`user-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L334) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | string; sparse array | `","` | | [`variation-selector-always-wide`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L532) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | server | flag | — | | [`visual-activity`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L718) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `both` | — | | [`visual-bell`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L727) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `both` | — | | [`visual-silence`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L736) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | `off`, `on`, `both` | — | | [`window-active-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L977) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | style string | — | | [`window-pane-current-status-format`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1522) | 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`window-pane-status-format`](https://github.com/tmux/tmux/blob/81f88f8517c9fc5371b56cf117530c6b477c96ac/options-table.c#L1529) | 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`window-size`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L986) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | `largest`, `smallest`, `manual`, `latest` | — | | [`window-status-activity-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1007) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`window-status-bell-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1016) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`window-status-current-format`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1025) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`window-status-current-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1032) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`window-status-format`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1041) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`window-status-last-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1049) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`window-status-separator`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1058) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | string | — | | [`window-status-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1065) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | style string | — | | [`window-style`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L998) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | style string | — | | [`word-separators`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L745) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | string | — | | [`wrap-search`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1075) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | | [`xterm-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1083) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | flag | — | ## Hooks | Name | Releases | Scope | Value | Array separator | | --- | --- | --- | --- | --- | | [`after-bind-key`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1092) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-capture-pane`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1093) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-copy-mode`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1094) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-display-message`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1095) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-display-panes`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1096) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-kill-pane`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1097) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-buffers`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1098) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-clients`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1099) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1100) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-panes`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1101) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-sessions`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1102) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-list-windows`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1103) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-load-buffer`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1104) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-lock-server`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1105) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-new-session`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1106) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-new-window`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1107) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-paste-buffer`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1108) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-pipe-pane`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1109) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-queue`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1110) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-refresh-client`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1111) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-rename-session`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1112) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-rename-window`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1113) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-resize-pane`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1114) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-resize-window`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1115) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-save-buffer`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1116) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-select-layout`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1117) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-select-pane`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1118) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-select-window`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1119) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-send-keys`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1120) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-set-buffer`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1121) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-set-environment`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1122) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-set-hook`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1123) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-set-option`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1124) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-show-environment`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1125) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-show-messages`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1126) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-show-options`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1127) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-split-window`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1128) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`after-unbind-key`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1129) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`alert-activity`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1130) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`alert-bell`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1131) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`alert-silence`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1132) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-active`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1255) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-attached`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1133) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-dark-theme`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1571) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-detached`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1134) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-focus-in`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1258) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-focus-out`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1259) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-light-theme`](https://github.com/tmux/tmux/blob/0dac7fe434d029a4f0b819cba8eb7963df291990/options-table.c#L1570) | 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-resized`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1135) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`client-session-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1136) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`command-error`](https://github.com/tmux/tmux/blob/ac44566c9c7e3e94d23be6def4c7ae83472543f5/options-table.c#L1350) | 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`pane-died`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1137) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-exited`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1138) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-focus-in`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1139) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-focus-out`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1140) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-mode-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1141) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-set-clipboard`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1142) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`pane-title-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1143) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window, pane | command; sparse array | empty | | [`session-closed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1144) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`session-created`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1145) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`session-renamed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1146) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`session-window-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1147) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`window-layout-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1148) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | command; sparse array | empty | | [`window-linked`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1149) | 3.2a | window | command; sparse array | empty | | [`window-linked`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1274) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | | [`window-pane-changed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1150) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | command; sparse array | empty | | [`window-renamed`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1151) | 3.2a, 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | command; sparse array | empty | | [`window-resized`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1277) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | window | command; sparse array | empty | | [`window-unlinked`](https://github.com/tmux/tmux/blob/3b929f332aafa7f1080eacc31feb11ffbb1d1841/options-table.c#L1152) | 3.2a | window | command; sparse array | empty | | [`window-unlinked`](https://github.com/tmux/tmux/blob/87fe00e8b44901240fc22d7120c1b31e4331f6f5/options-table.c#L1278) | 3.3, 3.3a, 3.4, 3.5, 3.5a, 3.6, 3.6a, 3.6b, 3.7, 3.7a, 3.7b, 3.7c | session | command; sparse array | empty | --- # Topology operations Source: https://libtmux.org/en/lua/latest/guides/topology/ > Source-owned Lua guide at 547c9c4228e0. Session, Window, Pane and WindowLink handles perform explicit mutations through the PROCESS endpoint. Each operation returns a Request that resolves to `true` when tmux successfully processes it. Refresh or capture a snapshot explicitly to inspect the resulting state; existing records do not change in place. ```lua assert(session:rename("build"):await()) assert(window:resize({ width = 120, height = 40 }):await()) assert(window:layout({ named = "tiled" }):await()) ``` The [integration fixture](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/tests/integration/topology.lua) includes setup, native-state assertions and cleanup through both runtime adapters. ## Names and removal `session:rename(name)` and `window:rename(name)` use their stable native IDs. Names are nonempty NUL-free strings up to 1,024 bytes. Session names also exclude dots, colons, ASCII controls and DEL. Format markers remain literal; native name validation and cleaning still apply. Releases differ in accepted bytes and escaping, so success does not promise byte-exact storage. Exact tmux 3.7 also rejects dots and colons in Window names. Renaming a Window turns off its automatic rename option. `session:kill()` destroys that session and its links. A linked window can survive in another session. `window:kill()` destroys the window and **every** link to it, potentially removing sessions left without windows. Neither method targets all other sessions or windows. Use these only for intended mutations; Request cancellation never implies either operation. ## Window navigation `session:navigate_window(direction)` accepts `"next"`, `"previous"` or `"last"`. Next/previous support `activity = true` to select a window with a native alert. Last rejects the `activity` option, including explicit false. Navigation changes the session's active window and follows native hooks and grouped-session behavior. `session:renumber_windows()` renumbers from the session's `base-index` option. It preserves Window IDs but invalidates captured link indices. It is a separate operation from moving one window. ## Window placements A WindowLink identifies one placement by session, index and Window ID. Use `creation.window_link` or a handle from `snapshot.window_links` when an operation needs that exact placement, including duplicate links in one session. `link:select()` selects its index within its session. `link:link(destination)` creates another placement; `link:move(destination)` also removes this source placement. Destinations accept one of these plain records: - `{ session = session, index = 5 }` chooses an explicit index. Omitting `index` requests a native free index from `base-index`, not append. - `{ link = anchor, position = "before"|"after" }` inserts relative to an existing placement and may shift indices. - `{ link = victim, position = "at" }` requires `{ replace = true }` as the operation options. Replacement can destroy the victim Window if it has no other links. A numeric index alone never authorizes replacement. Both operations default to `select = false`. Removal of an active source or replacement of an active destination can still force native selection. Occupied numeric destinations return native errors; the library never searches for a different destination or retries. `link:swap(other)` exchanges the Windows in two placements. By default, selected **slots** remain selected, although their Window identities change. `select = true` selects the destination slot, and the source slot when the sessions differ. Swapping two placements of the same Window is a native no-op. `link:unlink()` removes only this placement and refuses native last-link destruction. `kill_if_last = true` permits that destruction. Grouped sessions retain tmux's synchronization and last-link rules. Native refusal is not rollback: insertion can shift indices before a later grouped-session error. Each operation checks the stored source tuple, and any destination link tuple, in the native command queue immediately before the mutation. A recognized mismatch returns `stale_target`, `effect = "not_sent"`, and the native receipt. Old handles never rebind automatically after index reuse, movement or swapping. Recreating the identical tuple cannot be distinguished from continuous identity; native command aliases can also change these checks. The [native-command contract](../commands/) applies to the generated guards and their mutation branches. They are not a transaction or an unconditional compare-and-swap guarantee. Success returns `true`; capture a new snapshot explicitly to find resulting placements. No predicted index or hidden post-mutation read constructs a new WindowLink handle. ## Window size and layout `window:resize(options)` accepts exactly one form: - `width` and/or `height`, integers from 1 to 10,000. - `direction = "left"|"right"|"up"|"down"`, with `amount` from 1 to 10,000, defaulting to one. - `largest = true` or `smallest = true`, using tmux's native client-size rule. Native resizing sets `window-size` to manual. Layout constraints and available client sizes can affect the result; the command's success is not a dimensions oracle. Resizing affects every link to the Window. `window:layout(options)` also accepts exactly one form: - `named`: `even-horizontal`, `even-vertical`, `main-horizontal`, `main-vertical` or `tiled`. The mirrored main layouts require tmux 3.5+. - `layout`: an exported native layout string with a four-digit hexadecimal checksum, comma and body, bounded at 65,536 bytes. - `next = true`, `previous = true` or `restore = true`. Malformed custom-layout headers return `invalid_layout` before dispatch. This avoids faulty error handling in tmux 3.3/3.3a and short-header reads in older native parsers. Use `named` for standard layout names. Layout operations unzoom before native checksum and body validation. A rejected layout can therefore change zoom state. A nonzero native exit carries its receipt and `effect = "completed"`; it does not establish rollback. Custom layout syntax is tmux's grammar and is not evaluated as Lua or shell text. ## Restart a window `window:respawn({ context = link, ... })` requires a WindowLink naming this Window. Its session supplies the native launch context, including inherited environment. The link is checked in the native queue before respawn; the method never chooses an arbitrary session from a global Window ID. Launch options match [creation](../creation/): literal `argv` or explicit `shell`, absolute `cwd` and a per-process `environment` map. Omitting launch text reuses the previous command. Working-directory validation is asynchronous and completes before dispatch. `kill = true` permits replacement of running processes; otherwise tmux refuses an active window. Respawn retains the Window ID and its first Pane, removes sibling panes, and resets layout through every link to the Window. It can fail after destructive preparation; a native error does not establish rollback. Existing sibling Pane handles do not become references to the restarted first Pane. ## Move a pane `pane:move_to(target, options)` moves the same Pane into the target Pane's Window. It defaults to a vertical split with `select = false`. Choose `direction = "horizontal"`, `size` from 1 to 10,000 cells, or `percent` from 1 to 100. Size and percent exclude one another. `before = true` changes native geometry; it does not promise a matching pane-index order. `full_size = true` extends the split across the Window. `select = true` requires `target_link = link`, identifying the exact placement whose session and index will be selected. An optional target link also checks membership when selection is disabled. The library checks that placement and the target Pane's current Window separately in the native queue, then submits the compound target. A moved target Pane produces `stale_target`; a target that tmux cannot resolve can instead retain its native command error. Movement changes global pane membership through all Window links. Moving the last Pane destroys the old Window and all its placements. Native layout and selection changes can happen before a later error. Moving a Pane preserves its ID and running process; it does not restart that process. ## Break a pane into a window `pane:break_out(source_link, destination, options)` requires the Pane's exact source WindowLink. Destinations accept a Session with an optional numeric index, or an anchor WindowLink with `position = "before"|"after"`, as described under [window placements](#window-placements). Replacement is not supported; an occupied numeric destination returns the native error. The source placement and current Pane membership are checked in the native queue, along with any destination anchor. The operation preserves the Pane ID and running process. With multiple panes, tmux creates a new Window and keeps the source Window's links. With one pane, it moves the specified placement of the existing Window; other links to that Window survive. `select` defaults to false, though removal of an active placement can force native selection. `name` follows the Window name rules above. Omitting it preserves native naming: a singleton retains its existing Window name and options; a newly created Window takes the native default name and inherited options. An explicit name disables automatic rename for the resulting Window. Exact tmux 3.7 has a native multi-pane naming defect. The library supplies a placeholder when no name is requested, avoiding the faulty native null-name path. For a named multi-pane break, it follows the break with a rename of the same Pane's Window. This repair fires native rename notifications and `after-rename-window` hooks. Singleton breaks and other releases need no repair. A repair failure can occur after the Pane has moved; it is not rollback. ## Effects and boundaries Options are copied before dispatch and must be plain records. These methods accept the same nested `process` limits as [Pane operations](../panes/). Input is bounded at one MiB per operation. A stale generation or invalid target fails before dispatch. If continuity is lost after successful native completion, the error preserves `effect = "completed"` and that receipt. Mutations are never retried automatically. Native [aliases and hooks](../commands/) remain observable. These APIs do not promise transactions or protection against aliases that replace a built-in command. --- # MCP for Lua is not published Source: https://libtmux.org/en/lua/latest/mcp/ > The Lua repository contains an MCP scaffold, not a usable or published server. Lua has no published libtmux MCP server. The repository's MCP directory is a scaffold: it does not provide an installable server, executable, protocol catalog, or supported tool API. This page records availability so cross-language navigation does not turn a source placeholder into a product claim. There is no launch command, client configuration, tool reference, prompt catalog, or embedding reference to use. Use the Lua core library for direct tmux access through its luv or Neovim runtime. Choose another language's MCP server only as a separate process with that server's own package, policy, and compatibility requirements. --- # Workspace Manager for Lua is not published Source: https://libtmux.org/en/lua/latest/workspace/ > The Lua repository contains a workspace scaffold, not a loader or supported builder API. Lua has no published libtmux workspace manager. The repository's workspace directory is a scaffold: it does not provide an installable package, workspace loader command, supported file format, or product API. This page records availability without advertising placeholder code. There is no Lua equivalent of `libtmux-workspace validate`, `plan`, or `load`, and no workspace reference tree is generated. Use the core Lua API to create sessions, windows, and panes explicitly. A workspace tool from another language remains a separate application with its own configuration contract; it is not a Lua feature. --- # Third-party notices Source: https://libtmux.org/en/lua/latest/third-party-notices/ > Licences and attribution for the tools that build libtmux.org and the software libtmux depends on. libtmux and this site are built with open-source software. Several of those licences ask that their notice text travel with the work, so it is reproduced here. ## Documentation toolchain | Tool | Licence | Role | |---|---|---| | [Astro](https://astro.build) | MIT | The site shell | | [Tailwind CSS](https://tailwindcss.com) | MIT | Styling | | [Pagefind](https://pagefind.app) | MIT | Site-wide search | | [Expressive Code](https://expressive-code.com) | MIT | Code blocks | | [Sphinx](https://www.sphinx-doc.org) | BSD-2-Clause | Python and C++ reference | | [Furo](https://github.com/pradyunsg/furo) | MIT | Sphinx theme | | [Breathe](https://github.com/breathe-doc/breathe) | BSD-3-Clause | Doxygen XML into Sphinx | | [Doxygen](https://www.doxygen.nl) | GPL-2.0-only | Parses C++ headers to XML | | [API Extractor](https://api-extractor.com) | MIT | TypeScript API model | | [IBM Plex](https://github.com/IBM/plex) | OFL-1.1 | Typeface | ### A note on Doxygen Doxygen is licensed GPL-2.0-only. It runs as a build step that reads libtmux's own headers and emits XML; that XML is rendered by Breathe and Sphinx, and no Doxygen-generated HTML is published. Running a GPL program over your own input does not place its licence on the output, and libtmux does not distribute Doxygen or any modified version of it. ## Reference hosting Three ports deep-link to the canonical host their ecosystem already uses, rather than duplicating it here: - Rust: [docs.rs](https://docs.rs/libtmux) - Go: [pkg.go.dev](https://pkg.go.dev/github.com/libtmux/libtmux-go/tmux) - Java and Kotlin: [javadoc.io](https://javadoc.io/doc/io.github.libtmux/libtmux) Those sites are operated independently of this project and carry their own terms. ## libtmux itself Each port is MIT licensed. See the `LICENSE` file in that port's repository for the authoritative text. --- # Runtime ownership Source: https://libtmux.org/en/lua/latest/guides/runtime/ > Source-owned Lua guide at 547c9c4228e0. The runtime schedules asynchronous requests and owns their cleanup. Use its [connection and snapshot API](../snapshots/) for explicit capture. [Literal command execution](../commands/) shares that connection. Named domain operations and observation are still being integrated. Select `libtmux.runtime.luv` for a standalone Lua process. Its `run(body, limits)` function drives a quiescent top-level loop and returns `value, err` after owned work retires. It rejects coroutine entry, Neovim, active borrowed handles and nested calls from a Lua-driven luv loop. A C host that drives libuv without a Lua `uv.run` frame is outside this standalone contract. Importing the adapter does not import luv or start a loop. Select `libtmux.runtime.nvim` in Neovim. Its `start(body, on_done, limits)` function returns the runtime and root Request immediately. It borrows the host loop and schedules callbacks outside fast events. `on_done(value, err)` runs after owned cleanup; unrelated host handles remain open. ## Tasks and results The body receives its runtime. `runtime:spawn(body)` creates a child task and returns its Request eagerly. Tasks return `value, err`; a non-nil error or exception fails the enclosing task scope. Public library operations isolate operational failures in their returned Request; an unhandled failure in a caller's spawned task still fails the root. Returning normally joins child tasks, requests and deferred callbacks. A task may yield only through runtime operations; arbitrary Lua CPU work is not preemptible. `request:await()` returns `value, err` from a managed coroutine in the same runtime. Main-thread, foreign-coroutine and cross-runtime waits raise `invalid_await_context`. Multiple tasks can wait on one Request. `request:on_complete(fn)` registers a deferred `fn(value, err)` callback. Registration after settlement remains deferred while the runtime is live. Registration after root retirement or closure raises `closed`; exceeding callback or retained-byte limits raises `queue_full` synchronously. Callback failures fail their scope or appear in `runtime:errors()` when the scope has already failed or retired. `request:result()` reads a settled result without waiting; an unsettled request returns `nil, err` with code `pending`. `is_settled()` describes caller completion. `is_retired()` describes transport cleanup. Cancellation can settle before retirement, so these states differ. ## Cancellation and limits The task that creates an operation owns its cancellation. Canceling another task that waits on that operation detaches its wait; it does not cancel the producer. `request:cancel(reason)` cancels the requested operation explicitly. `runtime:close(reason)` rejects new work, cancels owned work and returns the root Request; repeated close calls are safe. Transport errors preserve `effect`: `not_sent`, `unknown`, or `completed`. Canceling a tmux client cannot prove that accepted daemon work stopped. Cleanup failures remain visible even when a result has already settled. | Limit | Default | | --- | ---: | | `max_active` | 16 | | `max_pending` | 128 | | `max_logical` | 128 | | `max_bytes` | 16 MiB | | `max_tasks` | 128 | | `max_callbacks` | 256 | | `max_resources` | 128 | | `dispatch_budget` | 64 | Pass overrides as the adapter's `limits` table. Runtime counters are exposed by `runtime:stats()`. Byte accounting includes queued callback and waiter delivery until transport retirement and delivery finish. Completed values retained by the caller belong to the caller's memory budget. Timers use monotonic milliseconds and reject delays above 2,147,483,647 ms. Logical Requests represent one-shot waits owned by the runtime, such as an observation's next event. They use the separate `max_logical` limit through retirement and do not occupy active or pending process slots. A normal root return joins these waits. Cancel them, close their producer, or supply a deadline when no further event is expected. Canceling a borrowed waiter still detaches that waiter without canceling the producer. Persistent native resources use private leases outside process slots. A lease can own one bounded byte reservation for its buffers; those bytes count toward `max_bytes` and `stats().resource_bytes`. Internal producers can release discarded bytes or transfer their reservation atomically to a same-runtime, unsettled Request before delivering its value. The total runtime charge stays unchanged during transfer and lasts through queued delivery. Closing the lease stops new retention and transfer; release remains available during cleanup. Cleanup must discard and release its buffers before reporting completion. Unreleased bytes report `cleanup_failed`, remain charged, and increment `resources_failed` even after the native close attempt ends. Leases and raw Request constructors remain private implementation APIs. Standalone host failures trigger bounded cleanup. If cleanup cannot finish, `cleanup_failed` reports remaining counters; it does not report successful retirement. The runtime never closes borrowed host loops or kills a tmux server as part of request cancellation. See [development checks](https://github.com/libtmux/libtmux-lua/blob/547c9c4228e07ebf846690eb44da2eeeb6fde922/.github/CONTRIBUTING.md) for real luv and Neovim host probes and [compatibility targets](../compatibility/) for pending lanes. LuaLS 3.19.1 completes runtime, Request, Server and snapshot chains in both adapter bodies, including related pane/window fields. It preserves the standalone `run` return type. That version does not infer the body result's members inside Neovim's separate `on_done` callback; annotate the callback's result parameter explicitly when editor completion is needed there. --- # Topics Source: https://libtmux.org/en/lua/latest/topics/ > Object traversal, cleanup, pane I/O, configuration, and failure handling. Use these pages for object traversal, cleanup, pane I/O, configuration, and failure handling. [Concepts](/concepts/) introduces the shared object model. The concept guides also cover [control mode vs one-shot](/concepts/transports/), [filtering and queries](/concepts/queries/), and [workspaces](/concepts/workspaces/). Choose a topic below for more detailed behavior: - **[Architecture](architecture/)**: locate operations and field definitions in each port's source. - **[Traversal](traversal/)**: navigate related objects, test membership, and compare identity. - **[Context managers](context-managers/)**: manage cleanup on block exit and identify objects that need an explicit kill. - **[Pane interaction](pane-interaction/)**: choose input modes, capture ranges, and completion waits. - **[Options and hooks](options-and-hooks/)**: configure tmux and register event commands at a supported scope. - **[Format-token fields](format-tokens/)**: read typed state and handle fields absent from a scope, version, or capture. - **[Waiting and retrying](waiting-and-retry/)**: wait on a condition or a named tmux signal. - **[Environment](environment/)**: locate objects from process variables and configure values inherited by new panes. - **[Socket and servers](socket-and-servers/)**: select a server, check liveness, and detect a replacement daemon. - **[Errors and exceptions](errors-and-exceptions/)**: handle command failures and determine whether a mutation can be retried. Use your port's API reference for signatures and defaults. These pages call out differences that affect how you use the APIs. --- # Architecture Source: https://libtmux.org/en/lua/latest/topics/architecture/ > The object hierarchy underneath the API you call, and who actually holds the behavior in each port. This page describes how the language ports organize their code and where operations live. For the object hierarchy and stable IDs, start with [Server, session, window, pane](/concepts/server-session-window-pane/). ## Where behavior lives: on the object, or through the server Python, TypeScript, Go, Rust, Java, .NET, and C++ provide operations on session, window, and pane objects. Each object carries its ID and server context. These examples send input, set an option, and kill a pane: Swift's `Session`, `Window`, and `Pane` are `Sendable` value types holding IDs and state fields. [Format-token fields](../format-tokens/) lists their fields. Perform operations through `Server`, passing the target value: The practical effect is that `Server` is the one thing you hold onto in a Swift program; a `Session` or `Pane` you got back from a `snapshot()` is inert data you hand back to the server that produced it, not a handle you call things on. Every other port's `Server` is also where you start, but `Session`/`Window`/`Pane` stay live actors once you have one. ## A generated data table under a hand-written surface Ports translate object IDs into tmux targets (`-t`) and read state through tmux's `FORMATS` variables (`#{...}`). Their field definitions use generated catalogs, fixed field sets, or captured dictionaries: | Port | Generated table | Hand-written surface | |------|-------------------|----------------------| | Python | `libtmux.constants` (`FORMATS`, gated by scope and tmux version) | dataclass fields on `Obj` (`libtmux.neo`), `None` when a gate excludes a token | | TypeScript | `packages/libtmux/src/_generated/format_fields.ts` (`{ scope, since, token }` per row) | camelCase aliases on `Pane`/`Session`/`Window` (`packages/libtmux/src/_generated/field_aliases.ts`) | | Go | `format_generated.go`, `option_generated.go` (built by `internal/generate/formats`) | `(value, bool)` accessor methods: Go's own "comma ok" idiom for a gate | | Rust | `formats.rs`'s per-token macro rows (`token, wire name, scope, kind, since version, absent-handling`) | typed methods returning `Option` | | Java | (typed field accessors generated for the query layer: see `Pane_`/`Session_` in [Filtering and queries](/concepts/queries/)) | `Optional` for fields introduced after a port's tmux floor | | C++, Swift | fixed field sets; see below | a fixed, curated set of non-optional struct/class fields | | .NET | a snapshot dictionary read at capture time | typed properties that throw `IncompleteSnapshotException` for a field the capture didn't request, rather than gating on tmux version per field | Swift and C++ expose fixed sets of state fields. Swift includes indices, dimensions, active state, command, path, and edge flags. C++ declares its fields in `kFields` arrays and uses `pane->expand("#{...}")` for other tokens. See [Format-token fields](../format-tokens/) for optional fields and tokens outside the fixed sets. ## Module layout, by port Each port's own top-level organization, to orient yourself before opening its source: - **Python**: one module per tier (`libtmux.server`, `.session`, `.window`, `.pane`, `.client`), plus `libtmux.common` for shared plumbing, `libtmux.neo` for the dataclass query layer, `libtmux.options` / `libtmux.hooks` as mixins every tier includes, and `libtmux.exc` for the exception hierarchy. - **TypeScript**: `packages/libtmux/src/{server,session,window,pane,client}.ts` hold the public classes; nearly everything they call into lives under `_internal/operations/` (one file per concern: `pane_io.ts`, `hooks.ts`, `options.ts`, `topology.ts`) and `_generated/` (the format/option/hook catalogs above). Separate packages in the same monorepo cover workspaces (`@libtmux/workspace`) and an MCP server. - **Go**: a single `tmux` package, split by concern into many files rather than many packages (`model.go` for the core structs, `lifecycle_kill.go`, `pane_capture.go`, `pane_geometry.go`, `hierarchy.go`, `plan_server.go` for folded invocations); `tmuxq` is a separate package for predicate queries over an already-read snapshot ([Filtering and queries](/concepts/queries/)), and `workspace` a separate one again. - **Rust**: `crates/libtmux/src/{server,session,window,pane}/` directories, each split into files by concern (a `settings.rs` per tier holding that tier's options-and-hooks methods, matching the pattern in [Options and hooks](../options-and-hooks/)); `hooks.rs`, `options.rs`, and `formats.rs` hold the shared, scope-generic machinery those call into. Workspaces and the MCP server are separate crates in the same workspace. - **Java**: `io.github.libtmux` holds `Server`, `Session`, `Window`, and `Pane` as `final` classes; each exposes its option and hook tables through `.options()` / `.hooks()` accessor methods returning a separate `Options` / `Hooks` view scoped to that object, rather than mixing those methods directly into the entity class the way Python and Go do. `Session_`, `Window_`, and `Pane_` are a parallel set of typed-field classes that exist only for the query layer. - **.NET**: `src/LibTmux/` gives every entity its own name (`Pane.cs`, `Session.cs`, ...) but splits each into several `partial class` files by concern rather than by inheritance: `Pane.Capture.cs`, `Pane.Input.cs`, `Pane.Relations.cs`, `Pane.Scopes.cs`, `Pane.Topology.cs`, and so on all contribute to one `Pane` type. `Options`/`Hooks` are reached through `.Options` / `.Hooks` properties, structurally the same idea as Java's accessor methods. - **C++**: `include/libtmux/entities.hpp` declares `Session`, `Window`, and `Pane` together as value types (`private Row` bases), with their method bodies in `src/` rather than the header; `server.hpp`, `options.hpp`, and `capabilities.hpp` are separate headers. A private `testing` component (`include/libtmux/testing/`) ships separately from the library proper: see [Context managers](../context-managers/) for what it's for. - **Swift**: `Sources/LibTmux/Server.swift` is the hub every operation extends; `Session.swift` and `Pane.swift` declare the thin value types, `Snapshot.swift` holds the relationship queries ([Traversal](../traversal/)), and `Options.swift`, `PaneInteraction.swift`, and `Mutations.swift` are `extension Server` files grouping options/hooks, send/capture, and kill respectively: all reachable only through `Server`, per the section above. ## Naming conventions Method names follow language conventions: Python, Rust, and C++ use `snake_case`; TypeScript, Java, and Swift use `camelCase`; Go and .NET use `PascalCase`. Option and hook names remain tmux's dash-separated strings, such as `automatic-rename`, regardless of the method's spelling. --- # Traversal Source: https://libtmux.org/en/lua/latest/topics/traversal/ > Moving up and down the server/session/window/pane tree, and the two questions that come up once you have more than one object. Use relationships to move between sessions, windows, and panes. [Server, session, window, pane](/concepts/server-session-window-pane/) explains the hierarchy and snapshot model. This page covers relationship calls, collection membership, and object identity. ## Down the hierarchy List children through the parent object or a captured snapshot. Whether a read issues another tmux command depends on the API, independently of whether the call is async; see [Server, session, window, pane](/concepts/server-session-window-pane/). | Port | Server → sessions | Session → windows | Window → panes | |------|--------------------|--------------------|-----------------| | Python | `server.sessions` | `session.windows` | `window.panes` | | TypeScript | `await server.sessions()` | `session.windows` | `window.panes` | | Go | `server.Sessions(ctx)` | `session.Windows()` | `window.Panes()` | | Rust | `await server.sessions()` | `session.windows()` | `window.panes()` | | Java | `server.sessions()` | `session.windows()` | `window.panes()` | | .NET | `server.GetSessionsAsync()` | `session.GetWindowsAsync()` | `window.GetPanesAsync()` | | C++ | `server->sessions()` | `session->windows()` | `window->panes()` | | Swift | `server.sessions()`, or `snapshot.windows(of: session)` for windows/panes once you have a `Snapshot` | see previous column | see previous column | TypeScript's `session.windows` and `window.panes` read the graph loaded by `await server.sessions()` without additional tmux commands. Rust also provides `server.attached_sessions()` to list only sessions with an attached client. ## All panes in a session Use a session-wide pane collection when the task spans several windows, such as finding a command or capturing output from every pane. A window linked to multiple sessions still refers to the same tmux panes. Check whether the API reads live state or traverses a captured graph before reusing its result. ### Python `libtmux.Session.panes` runs a session-scoped `list-panes -s` read. Use it to list panes across the session's windows without manually listing each window. ### TypeScript `session.Session.panes` returns a `Selection` from the session's captured graph. Reading it does not issue another tmux command; refresh the snapshot when you need newer state. ### Rust `session.Session.panes` performs a live listing and returns a `Result` from the async call. Handle a command failure before using the returned panes. ### Go `tmux.Session.Panes` reads captured relations without another tmux command. The relation must have been included in the read that produced the session; an uncaptured relation does not establish that the session has no panes. ### .NET `LibTmux.Session.Panes` reads the session's captured relations. It does not issue a tmux command; an incomplete capture may lack the required relation. ### C++ `libtmux::Session::panes` runs a session-scoped `list-panes` command. Inspect the returned result before traversing the panes. ### Swift `Snapshot.panes(of:)` accepts a session or a window. The session overload traverses the captured graph and deduplicates pane IDs without a tmux read. ### Java Java has no direct session-wide pane member. Traverse `Session.windows()` and then `Window.panes()` in the captured relations. Deduplicate pane IDs when combining results from sessions that may share linked windows. ## Up the hierarchy Parent lookups may read captured data or query tmux again. Check the method's read and failure semantics; [Server, session, window, pane](/concepts/server-session-window-pane/) introduces that distinction: | Port | Pane → window | Window → session | |------|----------------|--------------------| | Python | `pane.window` | `window.session` | | TypeScript | `pane.window` (getter, from the loaded graph) | `window.session` (getter) | | Go | `pane.Window()` → `(Window, bool)` | `window.Session()` → `(Session, bool)` | | Rust | `await pane.window()` → `Result, Error>` | `await window.session()` → `Result, Error>` | | Java | `pane.window()` | `window.session()` | | .NET | `pane.Window` (property) | `window.Session` (property) | | C++ | `pane->window()` | `window->session()` | | Swift | `pane.windowID`, then look it up via `Snapshot` | not a per-window field: join through the snapshot instead | Go and Rust return optional relationship results. .NET's `.Window`, `.Session`, `.ActiveWindow`, and `.ActivePane` read captured state synchronously and throw `IncompleteSnapshotException` if that capture lacks the required context. ## One walk, down and back up Start with a session, traverse to a window and pane, then look up the parent and compare its identity with the starting object: ## The active child "Which window is in front right now" and "which pane would a command actually reach" are common enough questions that most ports expose the active child directly rather than making you filter a list: | Port | Session's active window | Window's active pane | |------|---------------------------|------------------------| | Python | `session.active_window` | `window.active_pane` | | TypeScript | `session.activeWindow` (getter) | `window.activePane` (getter) | | Go | `session.ActiveWindow()` → `(Window, bool)` | `window.ActivePane()` → `(Pane, bool)` | | Rust | `await session.active_window()` → `Result, Error>` | `await window.active_pane()` → `Result, Error>` | | Java | `session.activeWindow()` → `Optional` | `window.activePane()` → `Optional` | | .NET | `session.ActiveWindow` (property) | `window.ActivePane` (property) | | C++ | `session->active_window()` | `window->active_pane()` | | Swift | filter for `isActive` on `snapshot.windows(of: session)`: `Window` carries its own `window_active` flag rather than the session exposing an accessor | same pattern, on the pane's own active flag | In Swift, filter snapshot children by `isActive`. Other ports expose an active-child method or property on the parent. [Format-token fields](../format-tokens/) describes the underlying `window_active` and `pane_active` fields. ## Is it in that collection? Checking membership generally goes through whatever your language uses for collection membership, since most of these calls already return an ordinary array, slice, or list: - **Python** overloads `in` directly on its `QueryList`: `window in session.windows`, `pane in window.panes`. - **Java**, **C++**, and **.NET** return standard collections. Use their standard membership operations with the identity comparison appropriate to the port. - **TypeScript** returns iterable `Selection` objects. Iterate over the selection and compare IDs, or spread it into an array for standard array operations. - **Go** and **Rust** return slices or vectors. Iterate and compare object IDs when testing membership by tmux identity. ## Is this the same object? Compare IDs to determine whether two handles refer to the same tmux object on the same server. Equality operators vary by port: | Port | How you check | |------|----------------| | Python | `window.window_id == other.window_id` (or `pane.pane_id == ...`) | | TypeScript | compare `.id` | | Go | `pane.ID() == other.ID()`: `PaneID` is a plain, `==`-comparable `string` | | Rust | `pane.id() == other.id()`: verified from the port's own doctests, not struct equality | | Java | `pane.equals(other)`: overridden to compare server identity plus pane ID | | .NET | `pane.Equals(other)`: overridden to compare a generation counter plus ID | | C++ | `pane == other`: `operator==` is defined directly on `Session`/`Window`/`Pane` | | Swift | compare `.id` for identity; see the equality note below | **Swift's equality compares captured state.** Compiler-synthesized equality for `Session`, `Window`, and `Pane` compares every stored property, including dimensions and current command. Two reads can compare unequal even when they describe the same tmux object. Compare `.id` when checking identity on the same server. --- # Context managers Source: https://libtmux.org/en/lua/latest/topics/context-managers/ > Scope-based cleanup for tmux objects, and when your program must kill them explicitly. A tmux session, window, or pane normally remains until you kill it. Scope-based cleanup can kill it when your code leaves a block, including after an exception. See [Workspaces](/concepts/workspaces/) for a temporary layout example. Python provides context managers for tmux objects. .NET provides ownership scopes for servers, sessions, and windows. Other ports require explicit cleanup or offer guards for test servers: | Port | Server | Session | Window | Pane | |------|:------:|:-------:|:------:|:----:| | Python | yes | yes | yes | yes | | .NET | yes | yes | yes | - | | Java | closes conn. | - | - | - | | Rust | test-only | - | - | - | | C++ | test-only | - | - | - | | TypeScript | - | - | - | - | | Go | - | - | - | - | | Swift | - | - | - | - | "test-only" means a guard owns an entire disposable test server. Java's `AutoCloseable` server releases its transport but leaves tmux running. A dash means no built-in cleanup scope is listed for that object; use an explicit kill call with the cleanup mechanism appropriate to your language. ## Python: every level, including nested Python's `Server`, `Session`, `Window`, and `Pane` support context managers. Entry returns the existing object; exit kills it, including when the block raises: Nested scopes exit in reverse order: pane, window, session, then server. ## .NET: an explicit ownership type, stopping at Window .NET's `OwnedSessionScope` and `OwnedWindowScope` wrap the created object and implement `IAsyncDisposable`. The `Session` and `Window` handles themselves are not disposable: There is no `OwnedPaneScope`. For tests, `TmuxTestFactory.CreateHierarchyAsync()` returns a `TemporaryHierarchyScope` containing a private server, session, window, and pane. Disposing it kills the server. ## Java: `Server` is closeable, but closing one doesn't kill it Java's `Server` implements `AutoCloseable`. Exiting `try (Server server = Server.open(config))` releases the owned transport while tmux and its sessions remain running. Kill sessions, windows, panes, or the server explicitly when your program owns their cleanup. ## Rust: no async `Drop`, so cleanup is explicit or best-effort Rust's `Drop::drop` is synchronous and cannot await an async tmux kill. Use explicit shutdown when you need to observe cleanup failures: - **`kill(self)` consumes the handle.** Session, window, and pane kill methods take `self` by value, preventing subsequent use of that handle. - **`libtmux::test::TestServer` provides a test guard.** Call `guard.shutdown().await?` to handle cleanup errors. Its `Drop` implementation falls back to synchronous, best-effort `force_cleanup()`. ## C++: RAII exists, but only for a private test server C++'s `Session`, `Window`, and `Pane` are non-owning values; destroying a handle does not kill its tmux object. `libtmux::test::ScopedTmuxServer`, in the separate `testing` CMake component, owns a private test server and its temporary socket directory: ## TypeScript, Go, Swift: no built-in scoping at all TypeScript, Go, and Swift require explicit cleanup of sessions, windows, and panes. Connection or notification handles may have separate disposal APIs: - **TypeScript** implements `[Symbol.asyncDispose]` on control connections and notification streams. `await using` releases those handles; it does not kill the watched session or pane. See [Control mode vs one-shot](/concepts/transports/). Use `finally` for a session your program owns: ```typescript const session = await server.newSession({ name: "work" }); try { const window = await session.newWindow({ name: "editor" }); await window.panes.at(0)?.sendKeys("echo hi"); } finally { await session.kill(); } ``` - **Go** implements `io.Closer` on `ControlClient`, `PaneObservation`, and `NotificationStream`. Use `defer conn.Close()` for those resources and an explicit `Kill(ctx)` for tmux objects: ```go session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "work"}) if err != nil { return err } defer session.Kill(ctx) // idiomatic Go: not a library-provided guarantee ``` - **Swift** uses non-owning session, window, and pane values. Call `try await server.kill(session)` or the corresponding window or pane overload when cleanup is required. ## What this means in practice Use explicit cleanup for objects whose handles have no disposal hook. For an entire disposable test server, prefer your port's test fixture or server guard; see [Testing with libtmux](/guides/testing-with-libtmux/). --- # Pane interaction Source: https://libtmux.org/en/lua/latest/topics/pane-interaction/ > Input defaults, screen capture, and waiting for a command to finish. Send input to a pane and capture its screen to interact with a running program. [Attach and send keys](/examples/attach-and-send-keys/) provides examples. [Sending keys](/guides/sending-keys/) and [Capturing output](/guides/capturing-output/) are task guides; this page compares input defaults, capture ranges, and completion handling. ## Typing into a pane Two questions come up every time you send something to a pane: should tmux press Enter afterward, and should tmux interpret what you sent as key names (`Enter`, `C-c`) rather than literal characters? Ports answer both, but disagree on whether that's one method with flags or two separate methods: ### Python **Type without Enter:** `pane.send_keys(text, enter=False)` **Type + Enter (default):** `pane.send_keys(text)` **How "literal" is chosen:** `literal=True` flag on the same method ### TypeScript **Type without Enter:** `pane.sendKeys(text, { enter: false })` **Type + Enter (default):** `pane.sendKeys(text)` **How "literal" is chosen:** `{ literal: true }` option ### Go **Type without Enter:** `pane.SendKeys(ctx, SendKeysRequest{Command: &text, SkipEnter: true})` **Type + Enter (default):** `pane.SendKeys(ctx, SendKeysRequest{Command: &text})` **How "literal" is chosen:** `Literal: true` field ### Rust **Type without Enter:** `pane.send_keys(keys)`: **always literal**, key names typed as text **Type + Enter (default):** `pane.send_line(text)` **How "literal" is chosen:** `send_keys` sends literal text; `send_key_names` interprets tmux key names. ### Java **Type without Enter:** `pane.send(keys)` **Type + Enter (default):** `pane.sendLine(command)` **How "literal" is chosen:** Separate text and key-sending methods. ### .NET **Type without Enter:** `SendKeysAsync(new SendKeysRequest(text, enter: false))` **Type + Enter (default):** `SendTextAsync(text)` (defaults `enter: true`) **How "literal" is chosen:** `Literal` field on `SendKeysRequest`; `SendTextAsync` hardcodes it ### C++ **Type without Enter:** `pane->send_text(text)` **Type + Enter (default):** `send_text(text)` then `send_key("Enter")` separately: no combined convenience exists **How "literal" is chosen:** `send_text` is always literal; `send_key` is always a key name ### Swift **Type without Enter:** `server.sendKeys([text], to: pane)` **Type + Enter (default):** `server.run(text, in: pane)` (sugar for `sendKeys([text, "Enter"], to: pane)`) **How "literal" is chosen:** `literally: true` option on `sendKeys` ### Examples **Rust's `send_keys` always sends literal text.** Use `send_key_names` for tmux key names. Passing `"Enter"` to `send_keys` types those characters; it does not press the key. **Text and Enter can be separate commands.** Python, TypeScript, Go, and .NET normally send Enter after the text. If the second operation fails, the text may already be in the pane; retrying the entire request can duplicate it. C++'s separate `send_text` and `send_key("Enter")` calls have the same risk. Rust's `send_line` and Java's `sendLine` append a literal `\r` to the text and send it in one `send-keys -l` command. They avoid a separate Enter dispatch. Send a command line and press Enter: ## Reading a pane back Capture methods return lines from the pane's visible screen by default: `pane.capture_pane()` in Python, `pane.capture()` in TypeScript, Rust, Java, and C++, `pane.Capture(ctx, ...)` in Go, `CaptureAsync(...)` in .NET, and `server.capture(pane)` in Swift. Request scrollback explicitly, such as with Python's `start` and `end` or Swift's `includingHistory`. ## Waiting for something to finish A send call completes when input reaches tmux. It does not wait for the shell command to finish. Wait for expected output or a completion signal: - **Python** can poll capture output for a marker. Its test-support module also provides `libtmux.test.retry_until(condition, ...)` for arbitrary conditions. [Waiting and retrying](../waiting-and-retry/) covers polling helpers across ports. - **Swift** ships a real primitive for exactly this: `server.waitForOutput(...)` returns an `OutputWait` once a pattern shows up in the pane, rather than leaving you to write the loop. - **Go** and **.NET** expose tmux's `wait-for` signal channel through `server.WaitFor(ctx, WaitForRequest{...})` and `TmuxWaitChannel`. Use a named signal when you control the command and can make it announce completion. [Capture pane output](/examples/capture-pane-output/) has the checked, per-port code for the polling-with-a-marker version of this; reach for a port's native wait primitive above it where one exists. --- # Options and hooks Source: https://libtmux.org/en/lua/latest/topics/options-and-hooks/ > Reading and writing tmux's own configuration knobs, and binding commands to its events, at whichever scope you're holding. Use options to change tmux behavior, such as `automatic-rename` or the status-line format. Use hooks to run commands on events such as `session-renamed` or `after-split-window`. Choose the scope supported by the option or hook. ## Reading and writing options Read an option, set it, or unset it at a chosen scope. Ports differ in how they distinguish values set locally from effective values inherited from another scope: ### Python **Read all (this scope):** `pane.show_options()` **Read effective/inherited:** `pane.show_option(name, global_=True)` reaches the global fallback explicitly; no separate "resolved" call **Set:** `pane.set_option(name, value)` **Unset:** `pane.unset_option(name)` ### TypeScript **Read all (this scope):** `pane.showOptions()` **Read effective/inherited:** `pane.showResolvedOptions()` **Set:** `pane.setOption(name, value)` **Unset:** `pane.unsetOption(name)` ### Go **Read all (this scope):** `pane.Options(ctx)`: a typed struct with one accessor method per option **Read effective/inherited:** Not listed. **Set:** `pane.SetOption(ctx, ...)` **Unset:** `pane.UnsetOption(ctx, ...)` ### Rust **Read all (this scope):** `pane.options()` (typed `BTreeMap`), `pane.option_names()` **Read effective/inherited:** `pane.typed_option(name)` decodes one value by its declared kind **Set:** `pane.set_option(name, value)`, `pane.append_option(name, value)` **Unset:** `pane.unset_option(name)` ### Java **Read all (this scope):** `pane.options().all()` **Read effective/inherited:** `pane.options().get(name)` reads `show-options -A -v`: inherited, not just local **Set:** `pane.options().set(name, value)` **Unset:** `pane.options().unset(name)` ### .NET **Read all (this scope):** `pane.Options.GetAllAsync()` **Read effective/inherited:** `pane.Options.GetAsync(new GetOptionRequest(name, includeInherited: true))`: an explicit opt-in flag, mapped straight to tmux's own `-A` **Set:** `pane.Options.SetAsync(new SetOptionRequest(name, value))` **Unset:** `pane.Options.UnsetAsync(...)` ### C++ **Read all (this scope):** `pane->options()` **Read effective/inherited:** Not listed. **Set:** `pane->set_option(name, value)` **Unset:** `pane->unset_option(name)` ### Swift **Read all (this scope):** `server.options(.pane(pane))` **Read effective/inherited:** `server.option(name, scope: .pane(pane))` reads presence from the listing, then the value with `-v` **Set:** `server.setOption(name, to: value, scope: .pane(pane))` **Unset:** `server.unsetOption(name, scope: .pane(pane))` ### Examples After a successful write completes, read the option to obtain its updated value. These examples set, read, and unset a pane option: ## Hooks ### Python **Set:** `pane.set_hook(name, command)` **Unset:** `pane.unset_hook(name)` **List:** `pane.show_hook(name)`, `pane.show_hooks()` (all) **Run now, without the event:** Not listed. ### TypeScript **Set:** `pane.setHook(name, command, { append })` **Unset:** `pane.unsetHook(name)` **List:** `pane.showHooks()` (all; no singular `showHook`) **Run now, without the event:** Not listed. ### Go **Set:** `pane.SetHook(ctx, name, command)`, `pane.SetHooks(ctx, ...)` (bulk) **Unset:** `pane.UnsetHook(ctx, name)` **List:** `pane.Hooks(ctx)`: typed struct **Run now, without the event:** Not listed. ### Rust **Set:** `pane.set_hook(name, command)` **Unset:** `pane.unset_hook(name)` **List:** `pane.hook(name)`: one name only; **no listing at pane/window scope**, by design (see below) **Run now, without the event:** Not listed. ### Java **Set:** `pane.hooks().set(event, command)`, `.append(event, command)` **Unset:** `pane.hooks().unset(event)` **List:** `pane.hooks().all()` **Run now, without the event:** `pane.hooks().run(event)`: tmux's `set-hook -R` ### .NET **Set:** `pane.Hooks.SetAsync(new SetHookRequest(event, command))` **Unset:** `pane.Hooks.UnsetAsync(...)` **List:** `pane.Hooks.GetAllAsync()` **Run now, without the event:** `pane.Hooks.RunAsync(...)` ### C++ **Set:** `session.set_hook(name, command)`: no `Window`/`Pane` overload exists at all **Unset:** Not listed. **List:** `session.hooks()`, `server.global_hooks()` **Run now, without the event:** Not listed. ### Swift **Set:** `server.setHook(name, to: command, at: index, in: scope)` **Unset:** `server.unsetHook(name, in: scope)` **List:** `server.hooks(scope)` **Run now, without the event:** `server.runHook(name, in: scope)` ### Examples tmux stores hook commands in indexed arrays, such as `after-new-window[0]`. Python and TypeScript can include the index in the name. Go's `SetHooks` and Swift's `at:` parameter take it separately. Java's `.append()` and TypeScript's `{ append: true }` append without requiring the next index. Set and list a session hook. The next section explains window and pane scope limitations: ## Supported hook scopes tmux stores hooks globally or per session. Accepted `set-hook -w` or `-p` flags do not imply a separate window or pane hook table, and `show-hooks` does not provide a corresponding listing. Check the event's supported scope if a hook is accepted but never fires. Ports handle unsupported hook scopes differently: - **Java's `Hooks.java`** documents that tmux can accept a hook at an unsupported scope without an effective registration. Check `.all()` when diagnosing a hook that does not fire. - **Rust** validates scope in `Pane::set_hook` and `Window::set_hook`, returning `Error::OptionScopeMismatch` for an unsupported scope. - **Swift** restricts `HookScope` to `.global` and `.session`. **C++** exposes `set_hook` on `Session` and `global_hooks()` on `Server`, with no window or pane hook methods. Options have window and pane tables of their own. The hook-scope limitation does not apply to ordinary options. ## tmux version compatibility Python's compatibility notes list these tmux requirements: | Feature | Minimum tmux | |---------|-------------| | All options/hooks features | 3.2+ | | Window/pane hook *scope flags* (`-w`, `-p`) accepted | 3.2+; see the supported-scope caveat above | | `client-active`, `window-resized` hooks | 3.3+ | | `pane-title-changed` hook | 3.5+ | Check your port's compatibility notes before relying on a particular tmux release. --- # Format-token fields Source: https://libtmux.org/en/lua/latest/topics/format-tokens/ > The typed fields every object exposes, mirroring tmux's own format tokens, and why a field is sometimes absent. Object fields expose values from tmux's [FORMATS](https://man.openbsd.org/tmux.1#FORMATS), such as `pane_id`, `window_zoomed_flag`, and `session_name`. The available fields depend on the port, object scope, tmux version, and data requested by the read. A token needs the right **scope** and **tmux version**. For example, a pane token needs a pane context, and a token added after your tmux release may be absent. Ports represent absence with optional values, flags, or errors, as described below. ## The absence idiom, per port | Port | What an excluded field looks like | |------|--------------------------------------| | Python | the attribute is `None` | | TypeScript | the property is `undefined` | | Go | a two-return-value accessor: `pane.DeadSignal()` returns `(string, bool)`: Go's own "comma ok" idiom | | Rust | `Option`; consult the reference for the accessor name | | Java | `Optional`: `pane.floating()` returns `Optional`, empty when the field isn't populated | | .NET | nullable values or `IncompleteSnapshotException`, depending on whether the value or captured field is absent | | C++, Swift | fixed, non-optional fields; see below for access to other tokens | These examples read optional fields, including `pane_dead_signal` on tmux 3.3 or newer: Rust's `formats.rs` marks this token as optional. Consult its generated reference for the accessor name. .NET also distinguishes missing values from incomplete captures. `Pane.Title` is nullable because tmux may report no title. `Pane.Height`, `.Width`, and `.Index` throw `IncompleteSnapshotException` when the read that produced the handle did not request those fields. A handle resolved by ID alone may therefore lack enough data to answer: ## A generated table under the accessor Several ports generate scope- and version-tagged field catalogs from tmux source or documentation. [Architecture](../architecture/) describes the layouts. Examples include: - **TypeScript** uses `_generated/format_fields.ts` rows with `scope`, `since`, and `token`. For example, `pane_zoomed_flag` has pane scope and requires tmux 3.7. `_generated/field_aliases.ts` supplies the camelCase alias `pane.zoomedFlag`. - **Rust** uses a macro row in `formats.rs` for each token's wire name, scope, tmux version, and type. `pane_dead_signal` has `Pane` scope, requires `V3_3`, and is decoded as `Text`. - **Go** generates `format_generated.go` with `internal/generate/formats`. Some accessors decode richer values: `pane.DeadTime()` returns `(time.Time, bool)` and performs timestamp parsing for the caller. Two per-token facts survive across every one of these catalogs, because they're facts about tmux, not about any one port's generator: `pane_dead_signal` and `pane_dead_time` arrived in tmux 3.3, and a cluster of pane-geometry and floating-pane tokens (`pane_floating_flag`, `pane_pb_progress`, `pane_x`, `pane_y`, `pane_z`, `pane_zoomed_flag`, `bracket_paste_flag`, `synchronized_output_flag`, among others) arrived together in 3.7. ## The two ports that didn't generate the full catalog Swift and C++ expose fixed, non-optional fields on `Session`, `Window`, and `Pane`: - **Swift** carries `index`, `width`, `height`, `isActive`, `currentCommand`, `currentPath`, and the four edge flags. - **C++** declares fields in `kFields` arrays. Pane fields include `id`, `command`, `active`, `index`, `title`, `pid`, `tty`, `path`, `width`, `height`, `dead`, `in_mode`, edge flags, and `piping`. Accessors return `std::string_view`, `bool`, or `long long`. For a token outside the fixed fields, C++ provides one-shot expansion with `pane->expand("#{pane_dead_signal}")`. Swift uses `FormatSubscription` on a control connection, delivering `SubscriptionChange` when tmux re-evaluates the token. That API observes changes over time. [Architecture](../architecture/) describes the fixed-field model. ## Fields promoted from the active child Python exposes fields promoted from an active child. For example, `session.pane_id` identifies the active pane of the session's active window: tmux's format engine includes active-child fields when listing a parent. A `list-sessions -F` row can include `window_id` and `pane_id` for the active window and pane. Check the port reference for typed access to those fields, or use the explicit relationships described in [Traversal](../traversal/). A pane context can include parent window and session fields. A session cannot identify one attached client when several clients may be attached, so client tokens such as `client_name` require a client context. --- # Waiting and retrying Source: https://libtmux.org/en/lua/latest/topics/waiting-and-retry/ > Polling a condition instead of guessing a sleep, and tmux's own wait-for signal channel as the alternative to polling. After sending input or starting a process, wait for the state your next step requires. [Pane interaction](../pane-interaction/#waiting-for-something-to-finish) covers waiting for screen text. This page covers arbitrary conditions and tmux's named `wait-for` signal channels. ## Polling a condition Polling checks a condition repeatedly until it succeeds or a deadline expires. The helpers below expose an interval and timeout; several live in test-support packages: ### Python **Helper:** `libtmux.test.retry_until(fn, seconds=, interval=)` **Where it lives:** The `libtmux.test` module in the main package; raises `WaitTimeout`. ### TypeScript **Helper:** `connectedServer.waitFor(matches, options)` **Where it lives:** The public control-connection API. Tests a predicate over `ServerSnapshot`; see [Control mode vs one-shot](/concepts/transports/). ### Go **Helper:** `tmuxtest.WaitFor(ctx, interval, condition)` **Where it lives:** `tmuxtest`, a separate test-support package from `tmux` ### Rust **Helper:** `libtmux::test::retry_until(within, condition)` **Where it lives:** `libtmux::test`, enabled with the `test-support` Cargo feature. ### Java **Helper:** Not listed. **Where it lives:** not found in the shipped library; a package-private `Await.until(...)` exists only inside the `integration-tests` module, which downstream code cannot depend on ### .NET **Helper:** `LibTmux.Testing.TmuxWait.UntilAsync(probe, timeout, interval)` **Where it lives:** `LibTmux.Testing`, part of the same shipped `LibTmux` package ### C++ **Helper:** Not listed. **Where it lives:** no generic condition-poll helper found in the public library; a `wait_until` exists only in the private `testing` component, for waiting on a spawned child process, not on tmux state ### Swift **Helper:** Not listed. **Where it lives:** a `waitUntil` helper exists only inside the test target's own support code, not shipped ### Examples Python, Rust, Go, and .NET provide general polling helpers in their test-support APIs. TypeScript's public `waitFor` instead waits on a server-snapshot predicate through a control connection. It subscribes before reading so it does not miss a change between those steps. For Java, C++, and Swift, this page lists no public arbitrary-condition polling helper. Use a loop with a deadline and interval if a more specific wait API does not fit; [Pane interaction](../pane-interaction/#waiting-for-something-to-finish) covers output waits. ## tmux's own wait-for channel Use `tmux wait-for -S ` to signal and `tmux wait-for ` to block until signalled. This avoids repeated screen captures when the command can announce its own completion: | Port | Signal | Wait | |------|--------|------| | Python | `server.wait_for(channel, set_flag=True)` | `server.wait_for(channel)` | | TypeScript | not exposed as public API: used only inside the test-server's own startup handshake | - | | Go | `server.WaitFor(ctx, tmux.WaitForRequest{Channel: name, Mode: tmux.WaitForModeSignal})` | `tmux.WaitForRequest{Channel: name}` (the zero-value `WaitForRequest.Mode` waits) | | Rust | `server.signal_channel(name).await?` | `server.wait_for_channel(name, timeout).await?` → `ChannelWait::Signalled` or `TimedOut` | | Java | `server.channel(name).signal()` | `server.channel(name).await(timeout)` → a `WakeReason`, never silently "success" | | .NET | `server.OpenWaitChannel(name)` returns a `TmuxWaitChannel`; signalling is the same request with a different mode | `await using` the channel, then `WaitAsync(budget)` | | C++ | `server.signal(channel)` | `server.wait_for(channel, timeout)` | | Swift | `try await server.signal(channel)` | `try await server.wait(for: channel)` | tmux remembers a signal sent before a waiter starts. The next wait on that channel returns immediately, so completion is not lost when the command finishes first. A raw `wait-for` client can exit zero when the server dies, as well as when the channel is signalled. Java's `WakeReason` and Swift's `wait(for:)` distinguish server loss from a signal; Swift checks the server PID before and after the wait. Use a channel name specific to the task, or clear an old signal with Java's `drain()` when appropriate. A remembered signal can otherwise satisfy an unrelated later wait. --- # Environment Source: https://libtmux.org/en/lua/latest/topics/environment/ > Locate tmux objects from process variables and manage the environment inherited by new panes. tmux exposes two environment APIs. Process variables such as `TMUX` and `TMUX_PANE` let code inside a pane identify its server and pane. The server also stores variables through `set-environment` and `show-environment` for new processes to inherit. Like the tables in [Options and hooks](../options-and-hooks/), this persistent store has explicit scopes. ## Locating yourself from inside a pane Inside a pane, `TMUX` contains `,,`, and `TMUX_PANE` contains the pane ID, such as `%1`. Use these variables to locate the current tmux objects. Ports expose different levels of environment lookup: ### Python **Server:** `Server.from_env()` **Session:** `Session.from_env()` **Window:** `Window.from_env()` **Pane:** `Pane.from_env()` ### TypeScript **Server:** Not listed. **Session:** `Session.fromEnv()` **Window:** Not listed. **Pane:** Not listed. ### Go **Server:** `NewServerFromEnv(env)` **Session:** `SessionFromEnv(ctx, env)` **Window:** `WindowFromEnv(ctx, env)` **Pane:** `PaneFromEnv(ctx, env)` ### Rust **Server:** `Server::from_env()` **Session:** `Session::from_env(&server)` **Window:** `Window::from_env(&server)` **Pane:** `Pane::from_env(&server)` ### Java **Server:** Not listed. **Session:** Not listed. **Window:** Not listed. **Pane:** See the Java context example below. ### .NET **Server:** `Server.FromEnvironment(env)` **Session:** `Session.FromEnvironmentAsync()` **Window:** `Window.FromEnvironmentAsync()` **Pane:** `Pane.FromEnvironmentAsync()` ### C++ **Server:** `Server::from_env()` **Session:** Not listed. **Window:** Not listed. **Pane:** Not listed. ### Swift **Server:** `TmuxContext.current()` **Session:** `TmuxContext.current()` (same call: see below) **Window:** Not listed. **Pane:** Not listed. ### Examples Python, Go, and .NET provide standalone environment lookups at each level. Rust's `Session`, `Window`, and `Pane::from_env` require an existing `&Server`. TypeScript provides `Session.fromEnv()`; this page lists no equivalent for its other object types. A process not started inside a pane has nothing truthful to answer with, so every one of these raises rather than guessing: Python's `NotInsideTmux`, Go's `FromEnvError`, .NET's `TmuxObjectNotFoundException`, and so on, each naming the missing or malformed variable rather than returning an empty or default object. ### Java and C++ stop short of the pane Neither port gives you a live object back the way the other five do, and they stop at different points: - **C++** provides `Server::from_env()` to select the socket. Use the resulting server to resolve sessions or panes. - **Java** parses `TMUX` and `TMUX_PANE` into identifiers: socket path, server PID, `SessionId`, and `Optional`. It returns context data rather than a live pane handle: ```java TmuxEnvironment here = TmuxEnvironment.current().orElseThrow(); try (Server server = Server.open(here.config())) { Session mine = server.sessions().stream() .filter(session -> session.id().equals(here.session())) .findFirst() .orElseThrow(); } ``` ### Swift context fields Swift's `TmuxContext.current()` parses the socket path, server PID, and session ID from `TMUX`. It does not read `TMUX_PANE`, so it cannot identify the current pane: Read `TMUX_PANE` separately if you need the pane ID; `TmuxContext` does not provide it. ## tmux's own environment variable store Like [Options and hooks](../options-and-hooks/#window-and-pane-hook-scopes-are-mostly-fiction), tmux's persistent environment store has global and per-session scopes. It is read with `show-environment` and updated with `set-environment`. Newly spawned processes inherit it; existing processes retain their own environments. ### Python **Set:** `server.set_environment(name, value)`, `session.set_environment(...)` **Read all:** `server.show_environment()`, `session.show_environment()` **Unset:** `server.unset_environment(name)`. See below for `.remove_environment()`. ### TypeScript **Set:** `server.setEnvironment(name, value)`, `session.setEnvironment(...)` **Read all:** `server.showEnvironment()`, `session.showEnvironment()` **Unset:** `server.unsetEnvironment(name)`, `session.unsetEnvironment(name)` ### Go **Set:** `server.SetEnvironment(ctx, name, value, opts)` (global, `-g`) **Read all:** `server.ShowEnvironment(ctx)` **Unset:** `server.UnsetEnvironment(ctx, name)` ### Rust **Set:** `server.set_environment(...)`, `session.set_environment(...)` **Read all:** `server.environment_all()`, `session.environment_all()` **Unset:** `server.unset_environment(name)`, `session.unset_environment(name)` ### Java **Set:** Not documented here; see the Java and C++ note below. **Read all:** Not documented here; see the Java and C++ note below. **Unset:** Not documented here; see the Java and C++ note below. ### .NET **Set:** `server.Environment.SetAsync(name, value)`, `session.Environment.SetAsync(...)` **Read all:** `server.Environment.GetAllAsync()` **Unset:** `server.Environment.UnsetAsync(name)`, `.RemoveAsync(name)` ### C++ **Set:** Not documented here; see the Java and C++ note below. **Read all:** Not documented here; see the Java and C++ note below. **Unset:** Not documented here; see the Java and C++ note below. ### Swift **Set:** `server.setEnvironment(name, to: value, in: scope)` **Read all:** `server.environment(scope)` **Unset:** `server.unsetEnvironment(name, in: scope)`, `.removeEnvironment(name, in:)` ### Examples Python's `set_environment` writes a value, and `unset_environment` (`-u`) removes the entry. `remove_environment` (`-r`) marks the variable for exclusion from new processes, including when tmux inherited it at server startup; the listing retains it as `-NAME`. Swift exposes the same distinction through `setEnvironment`, `unsetEnvironment`, and `removeEnvironment`. A remove operation is not documented here for TypeScript, Go, or Rust. ### Java and C++: no verified access to this table at all Java and C++ examples here cover environment values supplied at process creation, not reads or writes to an existing persistent table. Java's `SessionSpec.Builder.environment(Map)`, `WindowSpec.Builder.environment(Map)`, and `SplitSpec.Builder.environment(Map)` pass initial variables to `new-session -e`, `new-window -e`, and `split-window -e`. Consult the port reference if you need to change the environment of an existing session. --- # Socket and servers Source: https://libtmux.org/en/lua/latest/topics/socket-and-servers/ > How a port names one specific tmux server among several, checks whether it's actually there, and tells one running instance apart from another. A tmux server is selected by its Unix-domain socket. Use different sockets for independent servers, such as a development session and an isolated test server. Ports expose tmux's default, named (`-L`), and explicit-path (`-S`) socket selectors: ## Naming a server | Port | Default | Named socket (`-L`) | Explicit path (`-S`) | |------|---------|----------------------|------------------------| | Python | `Server()` | `Server(socket_name="work")` | `Server(socket_path="/tmp/tmux-1000/work")` | | TypeScript | `new Server()` | `new Server({ socketName: "work" })` | `new Server({ socketPath: "..." })` | | Go | `tmux.NewServer(tmux.ServerOptions{})` | `tmux.ServerOptions{SocketName: "work"}` | `tmux.ServerOptions{SocketPath: "..."}` | | Rust | `Server::new()` | `Server::builder().socket_name("work").build()?` | `Server::builder().socket_path("...").build()?` | | Java | `ServerEndpoint.defaultSocket()` | `ServerEndpoint.namedSocket("work")` | `ServerEndpoint.socketPath(path)` | | .NET | `new ServerConnectionOptions()` | `new ServerConnectionOptions(socketName: "work")` | `new ServerConnectionOptions(socketPath: "...")` | | C++ | `Server::at_default()` | `Server::at_socket_name("work")` | `Server::at_socket_path("...")` | | Swift | no bare default: see below | `Server(socketName: "work")` | `Server(socketPath: "...")` | Choose either a socket name or a socket path. TypeScript rejects both together with `TypeError`; Go documents that `SocketPath` takes precedence. tmux uses `TMUX_TMPDIR` to resolve the directory for default and named sockets. Swift requires an explicit `socketPath` or `socketName` argument. To reach tmux's default socket, use `Server(socketName: "default")`. Python's `Server(socket_name_factory=...)` and .NET's `ServerConnectionOptions(socketNameFactory: ...)` accept a callable that generates socket names. Use a unique name for each isolated test server. ## Is the server actually there? A server handle does not prove that the target server is running. Use a liveness check when your program needs to distinguish a live server from an unavailable socket: | Port | Check | |------|-------| | Python | `server.is_alive()` → `bool` | | TypeScript | `await server.isAlive()` → `Promise`; `await server.raiseIfDead()` throws with tmux's own reason instead | | Go | `server.IsAlive(ctx)` → `(bool, error)`: the `error` is reserved for a question that couldn't be answered at all, not for "not alive" | | Rust | `server.is_alive().await` → `bool`; `server.check_alive().await` is the fallible twin, for when the *reason* matters | | Java | `server.isAlive()` → `boolean` | | .NET | `await server.IsAliveAsync()` → `Task` | | C++ | `server.is_alive(timeout)` → `bool` | | Swift | `try await server.isRunning()` → `Bool` | TypeScript's `isAlive()` and Rust's `is_alive()` return a boolean. Use TypeScript's `raiseIfDead()` or Rust's `check_alive()` when you need failure details. ## Killing a server, and telling two apart Kill an entire server with Python's `server.kill_server()`, TypeScript's `await server.kill()`, Go's `server.Kill(ctx)`, Rust's `server.kill().await?`, Java's or Swift's `server.killServer()`, .NET's `await server.KillAsync()`, or C++'s `server.kill()`. Java's `Server.close()` only releases the local connection; see [Context managers](../context-managers/#java-server-is-closeable-but-closing-one-doesnt-kill-it). Two handles can select the same socket. Python's `Server.__eq__` compares `socket_name` and `socket_path`. Go's `server.Equal(other)` resolves relative paths and environment-dependent socket names against each handle's captured binding. A restarted server can reuse a socket path while having different state. TypeScript's `TmuxServerRestartedError` and Go's `ErrDaemonReplaced` detect a handle encountering a replacement daemon. --- # Errors and exceptions Source: https://libtmux.org/en/lua/latest/topics/errors-and-exceptions/ > What a failed tmux command becomes in each port, and the question every one of them has to answer before letting you retry it. A command can fail because tmux rejects it, or because the transport stops before returning a reply. Ports report these failures through typed exceptions or return values. Before retrying a mutation, determine whether tmux may already have received it. For lookup failures caused by zero or multiple matches, see [Filtering and queries](/concepts/queries/#the-cardinality-contract-side-by-side). ## A failed command, as a value | Port | How it fails | Base type | |------|--------------|-----------| | Python | throws | `LibTmuxException`: carries an optional `subcommand`; `str()` reads `": "` | | TypeScript | throws | `LibTmuxException extends Error`, with `TmuxCommandError` (tmux ran and refused) and `TmuxTransportError` (it didn't get an answer) as the two shapes that matter here | | Go | returns `(T, error)` | no shared base type: small typed `...Error` structs plus sentinel `errors.New` values, composed with `errors.Is` / `errors.As` and `%w` wrapping | | Rust | returns `Result` | one `Error` enum, `#[non_exhaustive]`, matched rather than caught | | Java | throws (unchecked) | `LibTmuxException extends RuntimeException` | | .NET | throws | `LibTmuxException`, with typed subclasses per failure (`TmuxCommandException`, `TmuxTransportException`, `TmuxObjectNotFoundException`, and a dozen more) | | C++ | returns `expected` | `CommandFailure { kind, delivery, exit_code, diagnostic }`: no exception type at all | | Swift | throws (typed) | `enum TmuxError: Error`, thrown as `throws(TmuxError)`: Swift's typed-throws syntax, not a bare `throws` | Rust and C++ return result values. Go returns an `error` that callers inspect with `errors.Is` or `errors.As`. Exception-based ports report failures through their exception hierarchies. TypeScript's own docs make the split between its two exception shapes concrete: ## Is it safe to retry? Retry a mutation automatically only when you know it was not dispatched, or when repeating it is safe for your operation. A timeout, cancellation, or dropped connection can occur after tmux has acted. These APIs expose delivery information: | Port | Name | States | |------|------|--------| | TypeScript | `TmuxTransportError.delivery` | `"not_started"` / `"written"` / `"replied"` / `"indeterminate"`: only `not_started` is safe to retry blindly | | .NET | `LibTmuxException.Dispatch` (`TmuxDispatchState`) | `NotDispatched` / `Dispatched` / `Unknown` (the default) | | Java | `DispatchOutcome`, via `TmuxTimeoutException.outcome()` | `NOT_DISPATCHED` / `COMPLETE` / `UNKNOWN` | | C++ | `DeliveryStatus` | `not_started` / `written` / `replied` / `indeterminate` | | Rust | `ControlModeErrorKind` (behind the `control-mode` feature) | `DispatchTimedOut` (safe to retry) vs. plain `TimedOut` (not: the connection may have already committed the command) | | Python, Go's one-shot path | - | inspect the exit status after normal completion; an interrupted call needs separate state verification | | Go's control-mode pool | handled internally, not exposed | a failed pooled connection is retired rather than reused, rather than handing the caller a retry-safety flag to check | | Swift | documented, not typed | `ControlSession`'s own doc comment states the same rule in prose ("a command that never reached tmux is safe to retry") without a dedicated enum | A state such as `not_started`, `NotDispatched`, `NOT_DISPATCHED`, or `DispatchTimedOut` identifies a request that did not reach tmux. Treat unknown delivery as potentially executed. C++ and TypeScript also distinguish `written`, where the transport accepted the request but no terminal reply has arrived. For subprocess calls that complete normally, inspect the exit status. If a call is interrupted or times out without a delivery state, check tmux's resulting state before repeating a mutation.