Custom workspace builders¶
A workspace builder turns an expanded workspace dict into a live tmux
session. tmuxp ships one builder,
ClassicWorkspaceBuilder, and uses it
by default. Advanced users can point tmuxp at a different builder, and packagers
can distribute builders that users select by name.
This is an advanced, opt-in feature. Existing workspace files keep using the classic builder with no changes.
See also
If you only want to set these keys in a workspace file, see Workspace builders in the configuration reference.
How a workspace is built¶
tmuxp loadreads the YAML/JSON file and expands it (shorthand, environment variables, trickle-down defaults).tmuxp resolves which builder to use from the
workspace_builderconfig key (default: the classic builder).tmuxp constructs the builder with the expanded workspace and a
libtmux.Server, then calls.build().The builder creates the session, windows, and panes, honoring plugin hooks and progress callbacks.
Selecting a builder¶
By dotted path¶
Point workspace_builder at an importable class. Both a
module:attr object reference and a dotted module.attr path are accepted:
session_name: my-session
workspace_builder: my_tmuxp_builders.builders:CustomBuilder
windows:
- window_name: editor
panes:
- vim
By entry-point name¶
Packaged builders register under the tmuxp.workspace_builders entry-point
group, letting users select them by a short name instead of an internal module
path:
workspace_builder: classic
The built-in classic builder is registered this way. A distribution registers
its own builder in pyproject.toml:
[project.entry-points."tmuxp.workspace_builders"]
mybuilder = "my_tmuxp_builders.builders:CustomBuilder"
Trusted import paths¶
When a builder lives outside tmuxp’s runtime environment (for example, a script
in your config directory), list trusted directories in
workspace_builder_paths. tmuxp expands ~ and environment variables,
resolves relative entries against the workspace file’s directory, requires each
entry to be an existing directory, and temporarily prepends them to sys.path
for the import and build:
workspace_builder: my_local_builder:CustomBuilder
workspace_builder_paths:
- ~/.config/tmuxp/builders
Warning
A workspace file that names a builder runs that builder’s Python code. Only load
workspace files you trust. tmuxp deliberately does not use
site.addsitedir() for these paths — that would execute .pth startup files
and is broader than making a module importable.
Writing a builder¶
The simplest custom builder subclasses the classic builder and overrides what it needs:
from tmuxp.workspace.builder.classic import ClassicWorkspaceBuilder
class CustomBuilder(ClassicWorkspaceBuilder):
"""A builder that renames the session after building."""
def build(self, session=None, append=False):
super().build(session=session, append=append)
self.session.rename_session(f"{self.session.name}-custom")
A builder written from scratch must satisfy
WorkspaceBuilderProtocol. The contract
covers what tmuxp load drives:
Constructor accepting
session_config,server, and the optionalpluginslist andon_progress/on_before_script/on_script_output/on_build_eventcallbacks.build(session=None, append=False)— create or populate the session; theappendpath adds windows to an existing session.session— the populatedlibtmux.Session.session_exists()andfind_current_attached_session()— used by the CLI for attach/append decisions.plugins— the list of plugin instances; honor the plugin lifecycle hooks (before_workspace_builder,on_window_create,after_window_finished).The
on_*callbacks — call them at the documented milestones so the CLI’s progress display andbefore_scriptoutput stay accurate.
The contract is synchronous today. It is shaped so an async builder can be added later as an additive extension without changing this surface.
Pane readiness¶
tmuxp waits for a pane’s shell prompt before dispatching layout and commands,
which avoids a zsh prompt-redraw artifact. That wait is only needed for zsh, so
it is configurable independently of which builder you use, through the
workspace_builder_options catalog:
workspace_builder_options:
pane_readiness: auto # auto | always | never (+ truthy/falsy aliases)
auto(default) — wait only when the session’s interactive shell is zsh.always(ortrue/on/yes/1) — always wait for default-shell panes.never(orfalse/off/no/0) — never wait; fastest, but accepts the prompt/layout race for shells that need it.
Panes with a custom shell or window_shell never wait, regardless of policy —
those run a command in place of an interactive shell, so there is no prompt to
wait for.
See PaneReadiness and
WorkspaceBuilderOptions for the parsing rules.
Testing a builder¶
Resolve, construct, and build against a real tmux server fixture:
from tmuxp.workspace import loader
from tmuxp.workspace.builder import registry
def test_custom_builder(server):
config = loader.expand(
{
"session_name": "demo",
"workspace_builder": "my_tmuxp_builders.builders:CustomBuilder",
"windows": [{"window_name": "main", "panes": [{"shell_command": []}]}],
},
)
builder_cls = registry.resolve_builder_class(config)
builder = builder_cls(session_config=config, server=server)
builder.build()
assert builder.session.name == "demo"
builder.session.kill()
For builders that live in a trusted directory, build the sys.path sandbox with
resolve_builder_paths() and
prepended_sys_path().
Choosing an approach¶
Classic builder — the default. Use it for any workspace that depends on strict, pane-by-pane side effects (
start_directory,shell,window_shell, pane environment).Readiness tuning — set
pane_readinessto trade prompt-safety for speed without swapping builders.A custom builder — when you need behavior the classic builder doesn’t provide. Keep dependency-sensitive setup in
before_scriptorshell_command_beforeif your builder relaxes ordering guarantees.