Workspace guide
libtmux-workspace 0.1.2 · Source
Load bounded YAML or JSON, inspect an immutable creation plan, then explicitly apply it to an open libtmux server.
API reference · Command reference · Guides · RubyGems
Part of libtmux for Ruby. Built on libtmux.
Check a workspace file, see the ordered steps a load would run, and create the
session on a running tmux server, from the libtmux-workspace command or from
Ruby.
Validation and offline planning need no running daemon, and imports start no tmux process.
Contents — Quick start · Write the file · Create the session · Failed apply · Attach or switch · Scripts · Leaves out · Support · Documentation
Quick start¶
Install the gem and its libtmux-workspace executable. It needs Ruby 3.3 or
newer, and tmux 3.3a or newer to load a plan:
$ gem install libtmux-workspaceSave this declared subset of tmuxp-style data as workspace.yaml. Relative
directories resolve against the configuration file’s directory.
session_name: workenvironment: PROJECT_MODE: developmentwindows: - window_name: editor window_index: 1 layout: tiled panes: - shell_command: printf 'editor ready\n' - {}Check the file. validate reads no tmux state:
$ libtmux-workspace validate workspace.yamlWorkspace configuration is valid.Print the ordered operations a load would run. Offline plan does
not contact tmux either:
$ libtmux-workspace plan workspace.yamloffline_create: 11 ordered steps1. create_session session (creation)2. set_session_option session (configuration)3. set_window_option window:0 (configuration)4. move_initial_window window:0 (configuration)5. split_pane pane:0:0 (creation)6. select_layout window:0 (configuration)7. send_command pane:0:0 (dispatch_only)8. unset_window_option window:0 (configuration)9. select_pane pane:0:0 (configuration)10. select_window window:0 (configuration)11. unset_session_option session (configuration)Write the file¶
Workspace.load: the declared subset¶
load reads a regular file of bounded size and infers JSON or YAML from its
extension. Workspace.parse takes the text and a base
directory instead. Both return an immutable workspace:
- Root:
session_name, nonemptywindows,options,window_options. - Window:
window_name, nonemptypanes,window_index,focus,layout,options. - Pane: a command string, or a mapping with
focus,splitandsize. - Root, windows and panes accept
start_directory,environment,shell_commandandshell_command_before. - Layouts are
even-horizontal,even-vertical,main-horizontal,main-verticalandtiled. - At most one window and one pane per window declare focus.
- Without a filename, the command looks for exactly one
.tmuxp.yaml,.tmuxp.ymlor.tmuxp.jsonin the current directory. - An unknown field or an unsupported feature fails with a
ConfigErrorthat names its position in the file. - YAML tags, anchors and aliases are rejected, and no Ruby, ERB, plugins or callbacks run.
${NAME}substitution is opt-in: passexpand_environment: true, environment: {"NAME" => "value"}toloadorparse, or--expand-environment --env NAME=VALUEto the command.workspace.to_hexports an immutable, normalized and reloadable configuration, with expanded values and absolute directories.
The full field, option and limit lists are in Workspace parsing.
Create the session¶
libtmux-workspace load: build it on a running server¶
load needs a running server. It never starts one, so start a private server
with a keepalive session and no user configuration:
$ WORKSPACE_TMP=$(mktemp -d)$ tmux -S "$WORKSPACE_TMP/tmux.sock" -f /dev/null new-session -d -s keepaliveCreate the workspace’s session on that socket. It stays detached:
$ libtmux-workspace load \ --socket "$WORKSPACE_TMP/tmux.sock" \ workspace.yamlWorkspace created; 11 steps completed. Shell commands were dispatched.The new work session holds the editor window:
$ tmux -S "$WORKSPACE_TMP/tmux.sock" list-windows -t workStop only the private server and remove its directory:
$ tmux -S "$WORKSPACE_TMP/tmux.sock" kill-server$ rm -rf "$WORKSPACE_TMP"Loading the same file again fails with status 1: a plan creates a new session and never reconciles an existing one.
Plan#apply: the same from Ruby¶
Workspace.load reads the file,
workspace.plan plans it against a snapshot, and
apply creates the session. Calling apply authorizes the configuration’s
shell commands.
The example file writes the same
document to a temporary workspace.yaml before loading it, so it runs saved on
its own:
require "libtmux"require "libtmux/workspace"require "tmpdir"
document = <<~'YAML' session_name: work environment: PROJECT_MODE: development windows: - window_name: editor window_index: 1 layout: tiled panes: - shell_command: printf 'editor ready\n' - {}YAML
Dir.mktmpdir("workspace-") do |directory| path = File.join(directory, "workspace.yaml") File.write(path, document) LibTmux::Server.start do |server| workspace = LibTmux::Workspace.load(path) plan = workspace.plan(snapshot: server.snapshot) puts "planned steps: #{plan.steps.length}" result = plan.apply(server: server) puts "applied: #{result.success?}" puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}" endendThe program runs on a private server and prints:
planned steps: 11applied: truesessions: work- A snapshot makes the plan reject a name that already exists, and every
applytakes a fresh snapshot and checks again. - The plan creates a new session. It does not reconcile, replace or remove an existing one.
- Success shows that tmux accepted the dispatch of each shell command. It does not show that the command finished.
The complete program also runs the
installed command with --switch. The contract is in
Workspace apply.
Recover from a failed apply¶
ApplyError#result: what exists after a failure¶
A failed apply raises ApplyError. Its immutable
result holds the completed step IDs, the
created_refs tmux returned, the
effects it observed, and the failed action. Pass compensate: true to remove
only the session the apply created:
require "json"require "libtmux"require "libtmux/workspace"
LibTmux::Server.start do |server| server.new_session(name: "borrowed", command: ["/bin/cat"]) crowded = LibTmux::Workspace.parse( JSON.generate({session_name: "crowded", windows: [{window_name: "small", panes: Array.new(40) { {} }}]}), format: :json, base_directory: __dir__ ) begin crowded.plan.apply(server: server, compensate: true) rescue LibTmux::Workspace::ApplyError => error puts "failed action: #{error.result.failed_action}" puts "created a session: #{error.result.created_refs.key?('session')}" puts "compensation: #{error.result.compensation}" end puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"endThat many panes do not fit in one window, so tmux refuses a split after the session exists. It prints:
failed action: split_panecreated a session: truecompensation: completedsessions: borrowed- Compensation removes the new session only after a tmux guard shows that every window and pane in it has a positively identified created reference.
- An unknown initial entity, or a borrowed entity moved into the session, makes it refuse.
- Compensation cannot undo what shell commands did. Without it, the partial state stays for you to inspect.
- Diagnostics omit command payloads and paths. Names are never used to guess ownership.
The command takes --compensate. The contracts are in
Workspace results.
Attach or switch to the new session¶
load --switch: move a client to the session¶
load --attachopens the CLI’s/dev/ttyafter creation and runs an owned terminal client until you detach. It requires a validTERM.load --switch CLIENTswitches a current tmux client to the created session after apply, keeping the session’s environment.CLIENTis a client name, a full TTY path or a TTY path without/dev/. Use--switch=VALUEfor a selector that begins with-.- A missing client, or a failure to open or attach the terminal, keeps the created session and returns status 3.
- Attach and switch are mutually exclusive, and neither infers a latest
client. The library’s
Plan#applydoes neither.
The selector rules are in Client switching.
Script it¶
--json: one object on stdout¶
Check the installed gem version without reading a configuration or contacting tmux:
$ libtmux-workspace --versionHuman plans list ordered operations and their effects. --json prints the
same plan data as Plan#to_h, including configured
command text and paths:
$ libtmux-workspace plan \ --json \ workspace.yamlload and plan --live act on an existing server. Without
--socket, they use the socket in LIBTMUX_SOCKET_PATH, else the name in
LIBTMUX_SOCKET, else the server TMUX names when run inside tmux, else
tmux’s default socket. When both variables are set, the path wins, an empty
variable counts as unset, and --socket ignores both. A command run from a tmux
pane with none of these set acts on that pane’s server. This detached creation
example names its socket with a TMUX_SOCKET value:
$ libtmux-workspace load \ --socket "$TMUX_SOCKET" \ --json \ workspace.yamllibtmux-workspace --help lists every option. The
command reference has a page for each command.
| Option | Effect |
|---|---|
--json¶ | Write one structured result or error object to stdout |
--socket PATH¶ | Select the existing server’s socket for load or plan --live; the default follows the socket variables, then TMUX, then tmux’s default socket |
--live¶ | Capture a snapshot before planning |
--timeout SECONDS¶ | Bound each apply, live capture or switch operation; the default is 5 |
--compensate¶ | Run guarded cleanup after an apply failure |
--attach¶ | Attach this CLI’s terminal after a successful load |
--switch CLIENT¶ | Switch an explicit current client to the new session after load |
--expand-environment¶ | Expand ${NAME} in directories and environment values; requires --env |
--env NAME=VALUE¶ | Supply a value for expansion; ambient environment variables are not copied |
| Exit status | Meaning |
|---|---|
| 0¶ | Validation, planning or apply succeeded; requested attach/switch succeeded |
| 1¶ | Execution failed before known application effects |
| 2¶ | Configuration or arguments are invalid |
| 3¶ | Application was partial or uncertain, or a later attach/switch/cleanup failed |
| 130¶ | Interrupted; available effect ledger is retained |
Apply errors include ApplyResult#to_h. Human
errors write the diagnostic and any ledger to stderr. Diagnostics omit
configuration payloads; plan rendering and canonical export contain those
values by design.
What it leaves out¶
- Reconciliation. A plan creates a new session and never replaces or removes an existing workspace. The contract is in Workspace plans.
- Shell command completion. Apply records a shell command as dispatched. Embedded newlines can execute while the text is inserted. See Workspace apply.
- Rollback of shell effects. Compensation removes only tmux objects the apply created. See Workspace results.
- Features beyond the declared subset. Plugins, ERB and callbacks are
rejected, and a
ConfigErrornames the position. - A guessed client. Attach and switch need an explicit client. See Client switching.
Support and status¶
Releases are numbered 0.1.N, and each release increments N. The API is not
settled: any release may change or remove exported identifiers, and the
changelogs mark each break. Pin an exact version to avoid one.
See the changelog, and the main
changelog
for changes that span gems.
- Ruby 3.3 or newer. The gem depends on the same version of
libtmux, JSON 3.0 and Psych 5.5. - tmux 3.3a or newer to load or apply a plan.
- Older daemons.
loadcreates nothing and prints the refusal with the floor and the remedy. With--json, the refusal is inerror.messageanderror.kindistmux_unsupported. - Linux, including WSL, and macOS. The library and the command share the core compatibility workflow: Ruby 3.3, 3.4 and 4.0 with tmux 3.3a through 3.8 on Ubuntu 24.04, with per-revision results. A push and a pull request run the oldest and newest of those releases, 3.3a and 3.8, and a release runs all of them. macOS is supported on a best-effort basis, and CI coverage for it is highly limited.
- History limit. On tmux 3.3a to 3.6 the reused initial pane keeps the
global
history-limit. Set the server’s global value before you apply to get one history size.
Documentation¶
| Reference¶ | Every public workspace class and method |
| Command reference¶ | A page for validate, plan and load |
| Guides¶ | Validate, inspect and explicitly apply a plan |
| Topics¶ | Format, planning boundaries, effects and failure ledgers |
| Public behavior¶ | Parsing, plans, apply, results and the command |
| Recipes¶ | The workspace plan, load and failure program |