Core LibraryunreleasedGuides

Choose documentation 1

latest

Current version

latest
English

Unreleased libtmux_erlang has not been released. Its API may change during development. This site's structure and URLs may change too.

Owned operations

A successful send or command frame does not prove that a terminal program processed input. This example runs an owned job that acknowledges terminal parsing before it exits, then waits for tmux’s pane-died event, checks the exit status and captures the completed output.

The companion terminal job writes its result and an ANSI device-status request. It exits successfully only after reading tmux’s reply. tmux generates that reply after parsing the preceding output. The job writes no more output afterward. Python 3.12+ is an example dependency only.

pane-died alone is not a general output-drain barrier on tmux 3.3. This protocol applies to this cooperating job; it does not establish completion for an arbitrary program or reconstruct output that scrolled out of history.

The example changes options and a hook only on its new window. remain-on-exit keeps the pane addressable; an empty exit banner preserves the first line. The hook signals a tmux wait-for channel. There are no sleeps or capture retries. Run from the checkout so the companion job can be located:

Terminal window
$ mise exec -- escript tools/run_examples.escript job
-module(job_demo).
-moduledoc "Capture a job that acknowledges terminal parsing before it exits.".
-export([run/2]).
-spec run(binary(), binary()) -> map().
run(Socket, Executable) ->
Endpoint = #{socket_path => Socket, executable => Executable},
{ok, Server} = tmux_server:open(Endpoint),
tmux_scope:run_or_error(fun() ->
{ok, Session} = tmux_session:create(Server,
#{name => <<"jobs">>, width => 120, height => 30,
launch => #{argv => [<<"/bin/cat">>]}}),
tmux_scope:run_or_error(fun() ->
{ok, Window} = tmux_window:create(Session,
#{launch => #{argv => [<<"/bin/cat">>]}}),
Target = [<<"-w">>, <<"-t">>, tmux_model:get(id, Window)],
Deadline = #{deadline_ms => 2000},
{ok, _} = tmux_option:set(Window, <<"remain-on-exit">>, <<"on">>),
{ok, _} = tmux_option:set(
Window, <<"remain-on-exit-format">>, <<>>),
{ok, Hook} = tmux_command:raw(<<"set-hook">>,
Target ++ [<<"pane-died">>, <<"wait-for -S job-finished">>]),
{ok, _} = tmux_server:command(Server, Hook, #{}),
{ok, Wait} = tmux_command:raw(<<"wait-for">>, [<<"job-finished">>]),
{ok, Operation} = tmux_operation:start(Server, Wait, Deadline),
{error, #{code := await_timeout}} =
tmux_operation:await(Operation, 0),
JobFile = filename:absname("examples/terminal_job.py"),
Launch = #{argv => [<<"/usr/bin/python3">>,
list_to_binary(JobFile)]},
{ok, Job} = tmux_pane:split(Window, #{launch => #{shell => <<>>}}),
{ok, _} = tmux_pane:respawn(Job, #{launch => Launch}),
{ok, #{exit_status := 0, cleanup := complete}} =
tmux_operation:await(Operation, 900),
{ok, Completed} = tmux_pane:get(Server, tmux_model:get(id, Job)),
true = tmux_model:get(dead, Completed),
0 = tmux_model:get(exit_status, Completed),
{ok, <<"build complete\n">>} = tmux_pane:capture(
Completed, #{start => 0, 'end' => 0}),
{ok, Never} = tmux_command:raw(
<<"wait-for">>, [<<"not-signalled">>]),
{ok, Cancelled} = tmux_operation:start(Server, Never, Deadline),
{ok, _} = tmux_operation:await_started(Cancelled, 900),
{ok, cancel_requested} = tmux_operation:cancel(Cancelled),
{error, #{outcome := unknown, cleanup := complete}} =
tmux_operation:await(Cancelled, 900),
#{output => <<"build complete\n">>, job_status => 0,
await_timeout_kept_operation => true,
cancelled_outcome => unknown}
end, fun() ->
{ok, _} = tmux_session:kill(Session),
ok
end)
end, fun() ->
ok = tmux_server:close(Server)
end).

The output is build complete followed by a newline, and the exit status is zero. The zero-duration await proves that abandoning a wait leaves the operation usable. The separate cancelled wait reports an unknown tmux outcome and complete client cleanup; cancellation does not undo submitted work.

The example prepares an empty pane, then starts its job with tmux_pane:respawn/2. Respawn keeps the pane ID. Its default launch mode, previous, reuses tmux’s saved command; this example supplies new argv instead. Running panes require explicit force => true. Use a job-specific completion condition and fresh observations to check the result.

Use an empty shell command to prepare a process-free pane with tmux_pane:split/2. Session creation on every supported version, and window creation before tmux 3.8, can finish that command immediately and remove the new object. This example uses /bin/cat to keep its parent session and window available across versions.

await_started/2 returns lifecycle status, including when an operation already failed or retired. Use await/2 for its command result. Successful command results preserve separate stdout/stderr binaries, stdin bytes delivered to the client pipe, exit status, outcome and cleanup. Validation and wait errors can contain only a code; do not assume every error contains output or certainty. The native API defines t:tmux_operation:completed/0, t:tmux_operation:failure/0 and t:tmux_operation:state/0.

Treat a pane’s exit_status field independently from its dead flag. tmux exposes a numeric status after collecting a normal child exit; signal termination has no numeric value in that field. The captured absence value is none, which does not mean success. The native pane_dead_signal format can identify a signal when diagnosing an unexpected exit. This distinction appears in the tmux 3.3a status callback and the tmux 3.8 status callback.

Operation ownership

The process that starts an operation owns it. Other processes can await its handle without becoming owners. The endpoint confirms a transfer after monitoring the new owner and releasing the old monitor; the new process does not perform a handshake. Owner death and cancellation retire queued or possibly-sent clients; they do not replay mutations.

An await_timeout from tmux_operation:transfer/2 leaves the transfer outcome uncertain: the endpoint may process the owner change after the caller stops waiting. Read tmux_operation:status/1 to establish the current owner before relying on ownership. The timeout does not prove the old owner still owns the operation.

Retired results are retained for at most 60 seconds. The endpoint also evicts the oldest results when retention exceeds 64 results or 4 MiB of stdout/stderr. Use tmux_operation:status/1 while a handle is valid, and handle operation_expired after retention. Closing an endpoint retires its library clients and preserves the tmux server. Daemon ownership explains the separate token used to stop an owned daemon.

Current operations use one-shot clients. Persistent control, grouping and stream delivery are not yet available; see support and gaps.

Esc

Type to search.

Reference previewOpen reference