libtmux Reference MCP Search

Errors and exceptions

Edit this page on GitHub

A tmux command can fail two different ways: tmux runs it and refuses (a nonzero exit, a message on stderr), or something between your process and tmux goes wrong before an answer comes back at all — a timeout, a closed pipe, a cancelled call. Every port turns both into something typed rather than a bare string, but they split on how — thrown exceptions in some, returned values in others — and, more interestingly, on a question that only matters once you’re thinking about retrying: did tmux actually see this command or not?

Filtering and queries already covers the other common error shape — a lookup that matched zero or more than one object (ObjectDoesNotExist / MultipleObjectsReturned and their per-port names) — so it isn’t repeated here.

A failed command, as a valueLink to section

PortHow it failsBase type
PythonthrowsLibTmuxException — carries an optional subcommand; str() reads "<subcommand>: <stderr>"
TypeScriptthrowsLibTmuxException extends Error, with TmuxCommandError (tmux ran and refused) and TmuxTransportError (it didn’t get an answer) as the two shapes that matter here
Goreturns (T, error)no shared base type — small typed ...Error structs plus sentinel errors.New values, composed with errors.Is / errors.As and %w wrapping
Rustreturns Result<T, Error>one Error enum, #[non_exhaustive], matched rather than caught
Javathrows (unchecked)LibTmuxException extends RuntimeException
.NETthrowsLibTmuxException, with typed subclasses per failure (TmuxCommandException, TmuxTransportException, TmuxObjectNotFoundException, and a dozen more)
C++returns expected<T, CommandFailure>CommandFailure { kind, delivery, exit_code, diagnostic } — no exception type at all
Swiftthrows (typed)enum TmuxError: Error, thrown as throws(TmuxError) — Swift’s typed-throws syntax, not a bare throws

Rust and C++ read as the same idea in different clothes: neither throws, and both make the caller handle failure at the call site rather than letting it propagate silently through an unrelated catch. Go is the outlier even among the two Result-shaped ports — no single error type to match on at all, just many small ones and the standard library’s own composition tools.

TypeScript’s own docs make the split between its two exception shapes concrete:

Is it safe to retry?Link to section

Retrying a mutation is only safe if the first attempt never reached tmux — otherwise a retried kill-session or new-window can repeat a side effect that already happened. That’s easy to get right for a plain one-shot subprocess call that ran to completion: the exit code is the whole story. It stops being easy the moment a command can be cut off mid-flight — a timeout, a cancelled task, a dropped control-mode connection — because then “did tmux see it” genuinely has no default answer. Five ports encode this as an explicit, named state rather than leaving it to be inferred (or guessed) from whichever exception happened to come back:

PortNameStates
TypeScriptTmuxTransportError.delivery"not_started" / "written" / "replied" / "indeterminate" — only not_started is safe to retry blindly
.NETLibTmuxException.Dispatch (TmuxDispatchState)NotDispatched / Dispatched / Unknown (the default)
JavaDispatchOutcome, via TmuxTimeoutException.outcome()NOT_DISPATCHED / COMPLETE / UNKNOWN
C++DeliveryStatusnot_started / written / replied / indeterminate
RustControlModeErrorKind (behind the control-mode feature)DispatchTimedOut (safe to retry) vs. plain TimedOut (not — the connection may have already committed the command)
Python, Go’s one-shot pathnot needed: a plain subprocess call that returns is a call that ran; there’s no in-between state to name
Go’s control-mode poolhandled internally, not exposeda failed pooled connection is retired rather than reused, rather than handing the caller a retry-safety flag to check
Swiftdocumented, not typedControlSession’s own doc comment states the same rule in prose (“a command that never reached tmux is safe to retry”) without a dedicated enum

All four typed versions agree on the shape even where the vocabulary differs: exactly one state (not_started / NotDispatched / NOT_DISPATCHED / DispatchTimedOut) is ever safe to retry blindly, an “unknown” state defaults toward not retrying rather than assuming success, and a definite “tmux ran this” state means whatever the command does has already happened — the failure is tmux refusing or reporting an error, not the command going missing. C++‘s and TypeScript’s shared written state splits the middle ground further still: the request reached the transport but no terminal reply came back, which is closer to “assume it ran” than to “assume it didn’t.”

Where a port doesn’t expose this at all, it’s usually because the question doesn’t arise for how that port runs commands, not because the port ignored it: Python’s and Go’s default one-shot subprocess path returns only once the process has actually exited, so there’s no window in which “did it run” is unknown by the time you have a result to inspect at all. It becomes a live question the moment either port’s own control-mode or pooled-connection path is in use instead — Go handles that internally rather than surfacing a retry-safety value to the caller.

Esc

Type to search.