Workspace ManagerconceptGuides

Choose documentation 3

v0.1.0.alpha.3

Current version

v0.1.0.alpha.3
English

Prerelease This site documents an alpha of libtmux. Its structure, URLs and APIs are subject to change.

Workspace guide

libtmux-workspace 0.1.0.alpha.3 · 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 alpha and its libtmux-workspace executable. It needs Ruby 3.3 or newer, and tmux 3.3a or newer to load a plan:

Terminal window
$ gem install libtmux-workspace --pre

Save this declared subset of tmuxp-style data as workspace.yaml. Relative directories resolve against the configuration file’s directory.

session_name: work
environment:
PROJECT_MODE: development
windows:
- window_name: editor
window_index: 1
layout: tiled
panes:
- shell_command: printf 'editor ready\n'
- {}

Check the file. validate reads no tmux state:

Terminal window
$ libtmux-workspace validate workspace.yaml
Workspace configuration is valid.

Print the ordered operations a load would run. Offline plan does not contact tmux either:

Terminal window
$ libtmux-workspace plan workspace.yaml
offline_create: 11 ordered steps
1. 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, nonempty windows, options, window_options.
  • Window: window_name, nonempty panes, window_index, focus, layout, options.
  • Pane: a command string, or a mapping with focus, split and size.
  • Root, windows and panes accept start_directory, environment, shell_command and shell_command_before.
  • Layouts are even-horizontal, even-vertical, main-horizontal, main-vertical and tiled.
  • At most one window and one pane per window declare focus.
  • Without a filename, the command looks for exactly one .tmuxp.yaml, .tmuxp.yml or .tmuxp.json in the current directory.
  • An unknown field or an unsupported feature fails with a ConfigError that 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: pass expand_environment: true, environment: {"NAME" => "value"} to load or parse, or --expand-environment --env NAME=VALUE to the command.
  • workspace.to_h exports 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:

Terminal window
$ WORKSPACE_TMP=$(mktemp -d)
Terminal window
$ tmux -S "$WORKSPACE_TMP/tmux.sock" -f /dev/null new-session -d -s keepalive

Create the workspace’s session on that socket. It stays detached:

Terminal window
$ libtmux-workspace load \
--socket "$WORKSPACE_TMP/tmux.sock" \
workspace.yaml
Workspace created; 11 steps completed. Shell commands were dispatched.

The new work session holds the editor window:

Terminal window
$ tmux -S "$WORKSPACE_TMP/tmux.sock" list-windows -t work

Stop only the private server and remove its directory:

Terminal window
$ tmux -S "$WORKSPACE_TMP/tmux.sock" kill-server
Terminal window
$ 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:

require "libtmux"
require "libtmux/workspace"
LibTmux::Server.start do |server|
workspace = LibTmux::Workspace.load(File.expand_path("../workspace.yaml", __dir__))
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(', ')}"
end

The program runs next to the workspace.yaml above, on a private server, and prints:

planned steps: 11
applied: true
sessions: work
  • A snapshot makes the plan reject a name that already exists, and every apply takes 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(', ')}"
end

That many panes do not fit in one window, so tmux refuses a split after the session exists. It prints:

failed action: split_pane
created a session: true
compensation: completed
sessions: 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 --attach opens the CLI’s /dev/tty after creation and runs an owned terminal client until you detach. It requires a valid TERM.
  • load --switch CLIENT switches a current tmux client to the created session after apply, keeping the session’s environment.
  • CLIENT is a client name, a full TTY path or a TTY path without /dev/. Use --switch=VALUE for 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#apply does 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:

Terminal window
$ libtmux-workspace --version

Human plans list ordered operations and their effects. --json prints the same plan data as Plan#to_h, including configured command text and paths:

Terminal window
$ libtmux-workspace plan \
--json \
workspace.yaml

load and plan --live require --socket for an existing server. This detached creation example uses an explicitly supplied TMUX_SOCKET value:

Terminal window
$ libtmux-workspace load \
--socket "$TMUX_SOCKET" \
--json \
workspace.yaml

libtmux-workspace --help lists every option. The command reference has a page for each command.

OptionEffect
--jsonWrite one structured result or error object to stdout
--socket PATHSelect the existing server’s socket for load or plan --live
--liveCapture a snapshot before planning
--timeout SECONDSBound each apply, live capture or switch operation; the default is 5
--compensateRun guarded cleanup after an apply failure
--attachAttach this CLI’s terminal after a successful load
--switch CLIENTSwitch an explicit current client to the new session after load
--expand-environmentExpand ${NAME} in directories and environment values; requires --env
--env NAME=VALUESupply a value for expansion; ambient environment variables are not copied
Exit statusMeaning
0Validation, planning or apply succeeded; requested attach/switch succeeded
1Execution failed before known application effects
2Configuration or arguments are invalid
3Application was partial or uncertain, or a later attach/switch/cleanup failed
130Interrupted; 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 ConfigError names the position.
  • A guessed client. Attach and switch need an explicit client. See Client switching.

Support and status

Alpha. APIs may change between releases.

  • 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. load creates nothing and prints the refusal with the floor and the remedy. With --json, the refusal is in error.message and error.kind is tmux_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. 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

ReferenceEvery public workspace class and method
Command referenceA page for validate, plan and load
GuidesValidate, inspect and explicitly apply a plan
TopicsFormat, planning boundaries, effects and failure ledgers
Public behaviorParsing, plans, apply, results and the command
RecipesThe workspace plan, load and failure program
Esc

Type to search.

Reference preview Open reference