The rules tmux-workspace follows when it loads, converts, imports and reports.
The README has the commands to start
with, and libtmux.org has a page for each command.
Contents — Loading · Discovery, conversion and saving · Document rules · Imports · Output · Python · Subprocesses · Generated reference and development
- Loading resolves
~and defined$VARreferences against the environment the CLI runs in, and it does so in every value tmuxp does: names, directories, scripts, option and environment values, and pane commands. - An undefined variable is left as written, so the pane's own shell still sees it.
- Mapping keys are never expanded.
-Snames a socket path and-La socket name.-Swins when both are given.- Without either,
LIBTMUX_SOCKET_PATHand thenLIBTMUX_SOCKETchoose, and the path wins when both are set. A blank variable counts as unset. - With neither flag nor variable, no socket is passed and tmux picks the server:
the one in
TMUXfrom inside a pane, and its default socket otherwise. shellhands tmuxp the-Sor-La variable chose, because tmuxp reads neither variable. With none chosen, tmuxp followsTMUXitself.- An attached
loadand--appendfrom inside tmux still require the selected server to be the one inTMUX, and refuse otherwise.
- Ordinary loading creates or reuses a session.
- Append authenticates the inherited tmux daemon before resolving the current pane, then keeps that session across input files even if a script moves the pane.
- Socket aliases are accepted when they reach that same daemon.
- Failures report retained changes rather than claiming rollback.
load -2 forces 256-color support for tmux clients, attachment included.
Without it, tmux detects the terminal's capabilities. -8 fails before
workspace lookup: supported tmux releases removed 88-color mode.
- All input layouts are checked before scripts run or sessions change.
- Named layouts accept unique abbreviations; version-sensitive names use the running daemon, or the selected client when no daemon is listening.
- Classic saved layouts require a valid checksum, consistent geometry, a nonempty tree, enough pane cells and at most 256 nested splits.
- This rejects malformed geometry even when tmux could repair or prune it.
- JSON layouts require tmux 3.8 or later, valid cell structure and enough pane cells, with at most 200 nested objects. tmux can still reject a layout during application.
- A window naming no
layouttiles its panes; tmuxp instead stacks them, halving each split. - A window naming no
focuskeeps the first window active, which tmuxp does not. - A pane naming no
focusleaves the last pane of its window active, which is what tmuxp leaves: panes are created detached here and selected afterwards, rather than each split taking the cursor with it.
- Explicit window indexes are reserved before implicit windows receive free
indexes from
base-index. - Append reserves indexes from later input files and rejects collisions across all remaining inputs before running scripts or changing options.
- Temporary-window removal preserves requested indexes when
renumber-windowsis enabled, then restores its local or inherited setting. - Restoration failures appear in the partial result.
workspace_builder_options.pane_readinessacceptsauto(the default),always,never, or boolean aliases.autoandalwaysboth wait, whatever the pane's shell is, because the problem is that a shell owns its terminal only once it has drawn: a command sent before that is echoed by the tty and then redrawn by the line editor, so the pane shows it twice.- Readiness polling uses a two-second budget for the pane cursor to move from its origin before commands are sent. A probe and the polling pause can extend the elapsed wait beyond that budget.
- Cursor movement is a heuristic, not a guarantee that the shell is ready.
- A timeout warns and continues.
- Blank panes and explicit launch commands never wait.
- An unrecognised key under
workspace_builder_optionsis a warning, not a refusal. - A pane's
shelloverrideswindow_shell;pane_shellis an alias. - Setting both pane keys is an error.
- Pane-level
enter,sleep_beforeandsleep_afterset command defaults. - Command mappings can override them; each override carries to later commands in the same pane.
- A null sleep resets that delay to zero.
enter: falsetypes the command without executing it.
- Use
--helpon any command for its arguments. - Discovery checks local project files and the first existing global directory
from
TMUXP_CONFIGDIR, XDG and the legacy directory. - Conversion preserves extension fields and never executes workspace commands.
- Explicit file saves protect existing files unless
--forceis supplied. convertasks for confirmation before writing unless--yesis given;freeze --save-tonames its own destination and needs neither--yesnor a terminal to write it.
- Native loading rejects a key it does not implement before contacting tmux.
- A key starting with
x-, at any level, is the exception: a vendor extension, accepted, ignored, and preserved byconvert. - YAML anchors and merge keys expand into ordinary values, and date-like scalars stay text.
- A document holds one mapping; duplicate keys, cyclic aliases, nonfinite numbers and nonstring mapping keys are rejected.
- A file ending in
.jsonmust be JSON. - YAML expansion stops at 100 levels and 100,000 values.
import tmuxinator and import teamocil translate supported source shapes and
validate the resulting native workspace before writing stdout or a destination.
convert remains lossless document conversion and does not validate loading.
-
Tmuxinator window command arrays stay sequential commands in one pane; explicit
panescreate separate panes. -
Project
pre_windowarrays form one;-joined command, and windowprearrays form one&&-joined command before each explicit pane. -
pre_tabis accepted as an alias forpre_window. -
Teamocil
commandsarrays form one;-joined command; legacycmdandsplitsare accepted. -
Window options and the first requested window/pane focus are preserved; absent focus selects the first item.
-
Layouts and source order remain part of the translated workspace.
-
Imports record an absolute project directory from the invocation directory, including when
rootis omitted. -
Window roots resolve against that project directory, so moving the saved file does not change its working directories.
-
Directories need not exist during import; native loading still checks them.
-
Missing session names use the source filename without its extension.
-
Conflicting non-null aliases and malformed scalar or command shapes are rejected.
-
Lifecycle hooks, project
pre/post, endpoint/runtime overrides, named pane titles, Teamocilclear/filters and other unsupported fields are rejected before output. -
Tmuxinator expands ERB templates through Ruby before parsing, so unexpanded
<%markup is refused before output or overwrite; Teamocil evaluates no templates, so the same markup in a Teamocil source is ordinary text and is preserved. -
Java creates all panes before sending commands, so before-command synchronization cannot preserve the source delivery order and is rejected.
-
Tmuxinator
synchronize: afteruses nativeoptions_afterinstead. -
Window
prerequires explicit nonempty panes; otherwise Tmuxinator would omit that command. -
Move unsupported behavior into an explicit native workspace or keep using its source tool.
--jsonreturns one document;--ndjsontakes precedence when both are present.- Load and child-process output stream as escaped, flushed events.
- Machine output never includes ANSI styling or implicit prompts.
- Interactive editors and Python shells use the controlling terminal while their result remains on stdout.
- Every machine-readable record carries
schema_versionandcommand. - A
--jsonresult is one document; a--ndjsonstream is one record per line, each with aneventand asequencecounting from 1. - A committed transcript of a load's stream lives beside the tests, so a record that gains, loses or renames a field is a diff rather than a surprise.
A failure names one code. This is the whole set, shared with the other
ports of this tool, so the same condition answers with the same name whichever
one a script calls:
-
workspace_not_found— the named workspace is not where discovery looked -
invalid_workspace— the document parsed, and does not describe a workspace -
unsupported_key— the document uses a key native loading does not implement -
session_not_found— the session the command names is not on the server -
session_mismatch— the named session exists and is not what the document describes -
tmux_unavailable— tmux could not be found or run -
tmux_unsupported— tmux is older than 3.3a; the message says to upgrade tmux or use the previous release -
tmux_failed— tmux ran and refused, or the server changed under the command -
script_failed— a child program could not be run, or ended badly -
destination_exists— the destination a capture was told to write is taken -
usage— the command was invoked in a way that cannot be carried out -
A layout name is
invalid_workspacewhen no tmux accepts it, andtmux_failedwhen this daemon will not take one another would:main-horizontal-mirroredarrived in 3.5, andmain-hstopped being a unique abbreviation there. -
The document is the same either way; the daemon is not.
Declining a prompt writes nothing and is not an error; the command says what it
did not do. A prompt that cannot be
asked for want of a terminal is usage.
-
Names outside that set are not a verdict on the request.
-
interruptedsays the process was signalled and reports where it stopped, with exit 130. -
log_unavailablesays a--log-filesink that opened stopped accepting records; it is reported separately and leaves the command's own result and exit status alone. -
A
--log-filethat cannot be opened in the first place isusage, like any other invocation problem. -
A command's
statusis decided by what each of its results retained, not by which codes its errors carry. -
Loading two files where the first builds a session and the second does not match its own is
partial: the first session is still there.
An --ndjson stream carries these events:
-
started— the command's work began, withinputs -
workspace-started— one input began, with that input'seffectsso far -
session-created— a session this load owns now exists -
window-created— a window exists, withpane_totalstill to come -
pane-created— a pane exists; its commands have not been sent -
pane-completed— a pane has had its commands sent -
window-completed— a window and every pane in it are done -
workspace-completed— one input finished, with itseffects -
script-started— a child program is about to run -
script-output— a fragment of a child's output, naming itsstream -
script-completed— a child ended, withchild_statusandtruncated -
warning— something asked for that will not happen, with its owncode -
completed— the command finished, carrying its whole result -
failed— the command stopped, carrying the result up to the failure -
A load's
effectsrecord describes one input:input,input_index,session_id,session_name,reused,owned_session,changed,window_total,session_pane_total,window_ids,pane_ids, andstage— the point the load reached, one ofresolve,windows_preflight,before_script,options,readiness,windows,commands,finalize,completedorreused. -
A load that removed the session it created adds
session_removed. -
A Python-bridge load adds
engine,effects_scopeandeffects_unknown, because what it reports is observed topology rather than operations it performed.
The envelope's status is ok, partial or error. partial means effects
were retained; error means none were. The exit status carries the same
verdict:
0— the command finished, andstatusisok.1— it failed, andstatusiserrororpartial.2— ausagefailure: the invocation cannot be carried out.130— the process was interrupted.
Human output uses semantic colors. NO_COLOR disables styling, followed by the
explicit --color policy. Automatic color also recognizes FORCE_COLOR,
CLICOLOR_FORCE and CLICOLOR.
Terminal stderr displays event-driven load progress. --progress-format accepts
the default, minimal, window, pane and verbose presets, or a template.
TMUXP_PROGRESS_FORMAT supplies its default. Templates accept these fields:
-
Identity:
session,workspace_path,window. -
Position:
window_index,window_total,window_progress,pane_index,pane_total,pane_progress,progress. -
Completion:
windows_done,windows_remaining,window_progress_rel,pane_done,pane_remaining,pane_progress_rel,session_pane_total,session_panes_done,session_panes_remaining,session_pane_progress,overall_percent,summary. -
Bars:
bar,pane_bar,window_bar.status_iconis empty. -
Unknown fields stay literal;
{{and}}escape braces. -
The script panel shows the latest output while preserving its original stdout/stderr destination.
-
--progress-linesorTMUXP_PROGRESS_LINESsets its rows: 3 by default, 0 hides the panel, and -1 uses the available initial terminal height. -
Retained panel text is bounded to 65,536 characters.
-
Terminal dimensions are sampled once; the display does not track resizing, and Unicode clipping is conservative.
-
--no-progress,TMUXP_PROGRESS=0,TERM=dumb, redirected stderr and machine output disable the display. -
Completion and interruption clear its frame.
--log-levelfilters diagnostics from debug through critical, defaulting to warning.- Load accepts
--log-filefor appended JSON log records. - New files use private permissions on POSIX filesystems; existing permissions are preserved.
- Info-level logs include both captured bootstrap streams.
- Diagnostics use stderr and preserve machine stdout.
- Invalid log destinations fail before tmux changes.
- Runtime append or close failures produce a separate logging diagnostic and preserve the workspace result and exit status; a failed logger stops recording.
- Interrupting a command returns exit 130.
- Writes retain their order within each stream.
- Final output has a bounded delivery allowance after interruption; an undrained pipe can leave that output incomplete.
- Caller-supplied output streams remain open.
- If a stream ignores interruption, its pending write can finish later when the caller drains it; cancellation does not reap that write.
-
Python shell commands and extension workspaces use
TMUX_WORKSPACE_PYTHON(defaultpython3) with tmuxp 1.74.0 installed. -
The interpreter's libtmux must also declare
TMUX_OPTIONAL_FEATURE_POLICY_VERSION >= 2. No released libtmux does yet; it comes with tmux-python/libtmux#780. Until then, install libtmux from that source, or skipshelland extensions. -
The version is checked before any input workspace changes.
-
Nonempty
plugins,workspace_builder, orworkspace_builder_pathsselect the bridge. -
Builder paths resolve relative to the workspace file; custom builders receive their own configuration keys.
-
Plugin import or version failures stop the load instead of prompting to skip the plugin.
-
Ordinary native loading and read commands do not require Python.
-
This CLI also permits an explicit custom builder to omit
windows. -
Common fields still receive tmuxp expansion, and present windows receive inherited defaults.
-
The builder supplies its own topology when it omits that field.
-
Search uses Java regex syntax; it does not launch Python or promise identical Python
resemantics. -
Extension append receives the originally authenticated session ID, even if an earlier input moves the invoking pane.
-
Combining extension append with
before_scriptfails before Python or tmux starts: the pinned Python builder can delete a borrowed session when that script fails. -
Native scripted append and extension append without a script remain available.
-
Bridge results identify
engine: python,effects_scope: observed, andeffects_unknown: true. -
The bridge keeps
owned_session: false: observing a session does not establish ownership. -
Reported window and pane IDs were absent from the selected daemon's pre-child snapshot and appear in the target session after the child exits or is interrupted.
-
They do not describe arbitrary plugin actions, transient objects, or changes to other sessions.
-
Child output remains separate from native build events; the bridge does not produce native pane progress.
-
Java retains observed sessions after extension failure.
-
Python builders and plugins remain executable user code with their own mutation and cleanup behavior.
The Python bridge requires tmuxp 1.74.0 and a Python libtmux whose minimum is tmux 3.3a or newer and whose integer optional-feature policy marker is at least 2. Both the runtime probe and the child check it before extension or shell code runs; see the migration notes. Native loading retains the core floor and does not acquire control.
- Captured subprocesses retain bounded output and report truncation.
- Linux uses an owned
setsidsession to terminate descendants retaining captured output; other platforms use known process handles. - Successfully detached services with closed output streams are retained.
Generate metadata from the same graph that parses commands:
$ tmux-workspace --generate schemaGenerate Bash completion:
$ tmux-workspace --generate bash--generate zsh and --generate fish write the same completion for those
shells. It suggests commands, option names and accepted choice values; it does
not query running sessions or discover saved workspace names.
- Add
--jsonto receive aschema_version: 1artifact withcommand: generate,format: bash, the exact completion text inscript, andstatus: ok. --ndjsonreturns that artifact as onecompletedevent withsequence: 1; it takes precedence when both machine flags are present.- Without either flag, stdout contains the Bash script.
- Schema generation keeps its metadata document in every output mode.
For repeated CLI execution while editing, keep Gradle watching the sources:
$ ./gradlew :libtmux-workspace-cli:runDevelopment \
--args='--help' \
--continuous \
--configuration-cache \
--max-workers=2 \
--no-parallel \
-Dorg.gradle.continuous.quietperiod=50- Replace
--helpwith the command to exercise. - This task compiles separate development classes with javac and uses the JVM's first compilation tier for startup.
- It skips Error Prone and NullAway for the CLI classes and does not run tests.
- Published libraries, normal compilation, installation and
checkkeep their full checks and never consume these development classes. - The initial build includes setup; later edits reuse Gradle and the compiler.
- This JVM setting also limits later optimization in long-running commands.
- Use normal
runor installation tasks for representative runtime benchmarks.
Run the CLI checks with cached Gradle configuration:
$ ./gradlew :libtmux-workspace-cli:check \
--configuration-cache \
--max-workers=2 \
--no-parallel- The tests use isolated sockets under the Java test root.
- PTY regressions use Python 3 standard-library helpers; native application commands do not require Python.
- Set
TMUX_WORKSPACE_TEST_PYTHONto an interpreter with tmuxp 1.74.0 to enable the extension fixtures. - Tests replace
HOME; user-installed Python packages also need an explicitPYTHONUSERBASE. - The repository-wide
checkremains the full gate; a focused CLI check does not replace it.