Repository navigation
Releases: atomicstack/gotmuxcc
Release list
v0.4.0
control-mode correctness: pane output, hook frames, layout events and attach targets
this release fixes five defects found by auditing gotmuxcc against the tmux source at e880cf63 (next-3.9). nothing had regressed from the tmux upgrade itself — the suite passed against a freshly built next-3.9 before any change — but four of the five were long-standing and one is new with tmux 3.8.
fixes
refresh-client -Aand-rarguments are now quoted when tmux's parser would otherwise read them as a conditional keyword. tmux lexes a%-leading word as a keyword unless every character of it is%or a digit (cmd-parse.y, yylex), sorefresh-client -A %0:onwas a hard parse error andSetPaneOutput,EnablePaneOutput,DisablePaneOutput,PausePaneOutput,ContinuePaneOutput,SetMultiplePaneOutputsandReportPaneColorshad never worked against a real server. broken since tmux 3.0; six unit expectations asserted the unparseable strings, which is why nothing caught it. the quoting mirrors the lexer rule, so bare-t %0targets keep their existing wire format (c5bbfa3).- the router no longer pairs a hook's guard block with a queued request. tmux inserts a command run by a hook into the control client's own queue and guards it like any other, so it arrives as an unrequested
%begin/%endpair carrying a flags field of0; pairing that withpending[0]handed the request the hook's output and put every later reply off by one. blocks the client did not type are now tracked under a sentinel label and their output discarded (f9910fb). %layout-changeis parsed positionally, so lines carrying empty fields are no longer dropped entirely. tmux expands a fixed four-variable template, and#{window_raw_flags}is empty for any window that is not current, last, marked or zoomed and has no activity, bell, silence or modal pane — much the more common case — while both layout fields come back empty for a window whose only panes are floating, which is new with tmux 3.8 and reachable viabreak-pane -W(71034b7).- the control client now attaches by
#{session_id}rather than#{session_name}. tmux allows:and.in names again since166267c8, andattach-session -treads those as the session:window.pane separators, soNewTmuxreturned a client whose transport was already dead and the first call failed with "tmux exit" (1cce29e). Optionspassesshow-options -H, restoring user options registered as hooks that tmux7277712cbegan hiding. the flag exists only on tmux 3.8 and later, so a rejected flag falls back to the flagless call, and that error is the one reported if the fallback also fails (ce54bb1).
additions
- the layouts tmux accepts beyond the four gotmux exposes are available as
WindowLayoutMainHorizontalLayout,WindowLayoutMainVerticalLayout,WindowLayoutMainHorizontalMirrorandWindowLayoutMainVerticalMirror(8ca39b0). - pane and window flag characters are documented including tmux 3.8's
A(float above zoom) andO(modal pane), andSetControlFlagsdocumentsnew-layouts, the any-client flags, and that tmux splits the flag list on commas only (8ca39b0).
tests
- 344 tests and subtests passed with
-race -count=1, including the full tmux integration suite; none were skipped,go vet ./...is clean, and the suite was run against both a next-3.8 binary and a freshly built next-3.9 (8ca39b0). - 222
%begin … 0guard fixtures across 14 files claimed tmux sends0for replies to commands the client had typed, which real tmux never does; they are corrected to1. handshake and initial-attach fixtures, which genuinely carry0, are unchanged (f9910fb).
caveats
WindowLayoutMainVerticaldeliberately keeps its inherited value ofmain-horizontal. gotmux has the same name/value mismatch and gotmuxcc mirrors its public API, so changing the value would silently alter the layout every existing caller gets; useWindowLayoutMainVerticalLayoutformain-vertical(8ca39b0).- the
show-options -Hfallback for servers predating the flag is covered by unit tests only; it was not exercised against a real pre-3.8 server (ce54bb1). quoteArgumentnow quotes arguments such as%0:onthat were previously sent bare. bare pane targets are unaffected, but a caller comparing exact command strings will see the change (c5bbfa3).- the hook mis-pairing regression is covered by unit tests replaying a captured next-3.9 stream. an integration test was written and removed: reproducing it needs another request in flight at the instant the hook block lands, which does not happen reliably against a live server, so the test passed with the fix reverted (f9910fb).
- the minimum go version remains 1.22, and no exported signature was removed or changed.
upgrading
go get github.com/atomicstack/gotmuxcc@v0.4.0v0.3.0
cancellable polling, reliable shutdown, and aggregate window links
this release adds context support for client lookup, format evaluation and control setup, removes redundant aggregate listing scans, and fixes cancellation and shutdown when a transport write blocks (0769966).
additions
- added
ListClientsContext,DisplayMessageContextandSetControlFlagsContext; the existing methods retain their signatures and delegate throughcontext.Background()(0769966).
fixes
- successful aggregate window and pane queries now return directly, including empty output, without redundant session/window enumeration. fallback remains available after command errors and skips vanished resources (0769966).
- window fallback preserves distinct session/index links sharing one physical window id, including their contextual active state. cancellation during fallback returns the context error and no partial rows (0769966).
- one router-owned writer preserves pending-request and wire order while callers can cancel both queued commands and blocked sends. transport close can interrupt a blocked write, and router close joins the writer (0769966).
- stdout continues draining after terminal notifications so buffered output cannot strand transport cleanup (0769966).
tests
- 305 tests and subtests passed with
-race -count=1, including the full tmux integration suite, linked-window metadata, all three context methods, blocked writes, late-response correlation and terminal stdout draining; no tests were skipped (0769966). go vet ./...and formatting/diff checks passed. query-count reductions are covered by recording transports; latency was not benchmarked.
caveats
- once a command starts writing, cancellation abandons the caller’s wait; the command may still execute and retains its response slot. commands are never replayed automatically.
- a transport send error terminates the connection because a partial write can leave framing ambiguous. a tmux command error (
%error) leaves the connection usable. custom transports must makeClose()unblock concurrentSend()calls. - successful aggregate pane rows are preserved verbatim; the compatibility fallback continues to deduplicate panes by physical id. the minimum go version remains 1.22.
- tmux-popup-control should adopt this release with
make update-gotmuxcc, thread contexts through client/session resolution, and bound setup outside its cache mutex.
upgrading
go get github.com/atomicstack/gotmuxcc@v0.3.0v0.2.0
control-mode framing correctness, safe shutdown, and per-command cancellation
this release fixes a protocol bug that could tear down the control connection from ordinary pane content, two concurrency defects on the shutdown path (one of them an unrecoverable panic), and adds context support so a wedged command can be abandoned. it also exposes the tmux 3.8 floating-pane formats.
fixes
- the router treated
%begin/%end/%error/%exitas protocol frames even inside an open block, socapture-pane -poutput could be parsed as protocol. a captured line starting%exittore down the entire connection; a captured%beginmis-bound the next queued request; a captured%endwith a colliding command number completed a command early. the in-block check now runs first: while a block is open every line is command output until the exact closing guard arrives, matching the contract upstream tmux asserts inregress/control-notify-guard.shafter6db5175e(a6f21f3) - guards are now matched as whole keywords, so
%beginning,%errorsand%exitedare notifications again rather than malformed frames, andparseFramerequires exactly three decimal fields (a6f21f3) %end/%errorare matched on the full(time, number, flags)triple rather than the command number alone, and%exitis only honoured at depth 0 — tmux prints it from the client process afterproc_loopreturns, so inside a block it is always pane content (a6f21f3)Tmux.Close()wroterouter,transportandSocketwith no synchronisation whilerunCommandreadrouter, giving both a data race and a check-then-use nil dereference. the fields are no longer cleared at all:router.close()already fails every pending and in-flight request viafailAll, so clearing them bought nothing but the race.Closeis now idempotent and safe to call alongside in-flight commands (11bfb6d)- fixed a
send on closed channelpanic in the control transport:finish()closed the lines channel while the stdout forwarder — its only sender — could be parked on a send. the forwarder now owns the close. the panic landed on a library-owned goroutine, so consumers could not recover from it (995a77a)
additions
- per-command cancellation via
ListSessionsContext,ListAllWindowsContext,ListAllPanesContext,CapturePaneContext,ListSessionsFormatContext,ListWindowsFormatContext,ListPanesFormatContextandCommandContext. existing signatures are unchanged and delegate throughcontext.Background(), so this is purely additive. the context reaches every command in theListAllWindows/ListAllPanesfallback chains, not just the first (cbf3afb) DefaultHandshakeTimeout(10s) andWithHandshakeTimeoutbound the initial control-mode handshake, so a tmux that neither completes the handshake nor closes the transport can no longer wedgeNewTmux/DefaultTmuxforever. a non-positive duration disables the bound (c00feb1)- floating and modal pane formats, which tmux 3.8 otherwise leaves indistinguishable from tiled panes in
list-panesoutput:Pane.FloatingFlag,ModalFlag,Z,Flags,X,Y,UnzoomedWidth,UnzoomedHeight, andWindow.ModalPane,ManualWidth,ManualHeight. all additive, all zero-valued on tmux versions that do not know the formats (61b274b)
notes
- the handshake bound is a behaviour change: a constructor that previously hung forever now returns an error after 10s. that is the point, but
WithHandshakeTimeout(0)restores the old behaviour if you need it - abandoning a command via context is deliberately caller-side only. the command has already been written to tmux and cannot be unsent, so the request stays in the router's pending queue and its reply is discarded on arrival — removing it would desynchronise response correlation, which pairs each
%beginwithpending[0] - the
godirective stays at 1.22; nothing here needs a newer toolchain
internal
- seven list-and-convert methods and seven find-by-field lookups each hand-rolled the same loop per entity type; both families now share unexported generic helpers, removing 51 lines with no change to any exported declaration (1155447)
GetClientByTtywas the only lookup returning its error unwrapped; it now wraps like the other six, so its error string has changed (the wrap uses%w, soerrors.Is/Asare unaffected) (1155447)
tests
- the router framing reproduction, replayed from a real tmux next-3.8 stream, plus exact-triple matching, substring-prefix cases, malformed guard shapes and transport EOF without
%exit(a6f21f3) - an end-to-end integration test that captures a pane displaying a control-mode transcript and asserts the connection survives it (a6f21f3)
Closedriven concurrently with a command loop, failing under-raceon the previous code (11bfb6d)- the transport forwarder parked on a full channel while the fake tmux writes to stderr and exits, reproducing the panic (995a77a)
- cancellation and deadline coverage, including that an abandoned request does not desynchronise the router and that an already-cancelled context never reaches tmux (cbf3afb)
- characterisation tests for six list/lookup functions that had 0% coverage before the refactor touched them (8d103a5)
upgrading
go get github.com/atomicstack/gotmuxcc@v0.2.0v0.1.4
prevent orphaned tmux -C subprocesses on abnormal consumer exit
previously the control transport spawned a long-lived tmux -C attach-session child that was reliably killed only by an explicit (*Tmux).Close(). a consumer that exited without Close — crash, signal, or a short-lived subcommand — orphaned the child, leaving it attached to the tmux server until the server died. in practice this showed up as dozens of accumulated orphans saturating the single-threaded tmux server.
fixes
- add build-tagged
sysProcAttr()helpers applied to the spawned command: linux setsPdeathsig: SIGKILL(the kernel reaps the child on parent death) plusSetpgid; darwin/bsd setSetpgidonly, since there is no pdeathsig equivalent; non-unix returns nil (440c9d0) - derive an internal cancelable lifetime context in
New, cancelled byClose()and on child exit, giving a secondCommandContext-based kill path independent ofProcess.Kill(440c9d0)
additions
- expose
NewTmuxContext(ctx, socket, ...)so consumers can bind a client's lifetime to e.g.signal.NotifyContext(440c9d0)
notes
- calling
Close()remains mandatory on macOS/bsd, where there is no parent-death signal to fall back on
tests
- platform helper coverage (
Setpgideverywhere,Pdeathsigon linux) - that
NewwiresSysProcAttronto the command - that
NewTmuxContextthreads its context through to the dialer
upgrading
go get github.com/atomicstack/gotmuxcc@v0.1.4v0.1.3
exact session-id targeting in session helpers
this patch release makes session-scoped operations target tmux sessions by id rather than by name, so overlapping session names can no longer be resolved via tmux prefix matching.
fixes
- prefer session ids over session names when issuing tmux commands from
Sessionhelpers, using an id-first target helper that falls back toNamefor manually constructedSessionvalues without anId(38536dc) - refresh
Session.Nameafter a successfulRenameso follow-on operations do not depend on stale local state (38536dc)
tests
- unit coverage for id-first targeting and the name fallback path
- an integration test reproducing the reported
claude/claude2name collision, verifying only the intended session is removed
upgrading
go get github.com/atomicstack/gotmuxcc@v0.1.3v0.1.2
Security hardening for control-mode protocol
This patch release hardens the tmux control-mode protocol layer against injection attacks.
Changes
- Harden control-mode protocol against newline injection — reject or sanitize inputs containing newline characters that could break the control-mode framing protocol (43c5cdc)
- Add format string quoting and security regression tests — quote tmux format strings to prevent interpretation of user-controlled data as format variables, with regression tests covering injection vectors (97db0a6)
- Harden control-mode protocol against injection — additional hardening across the protocol layer to prevent command injection via crafted session/window/pane names (91bf5db)
Maintenance
- Add
.worktrees/to.gitignore(e97ef58)
Upgrading
go get github.com/atomicstack/gotmuxcc@v0.1.2v0.1.1
empty-server control-mode startup fix
this release fixes a constructor bug in gotmuxcc.NewTmux() / NewTmuxWithOptions() when connecting to a tmux server that has no existing sessions.
previously, gotmuxcc could fall through to bare tmux -C during startup. tmux interprets that as an implicit new-session, which created an unwanted session as a side effect of opening the control-mode connection.
fixes
- use an explicit startup plan instead of falling through to bare
tmux -C - attach to an existing session when one is available
- when no sessions exist, create a uniquely named detached bootstrap session instead of consuming the next numeric session name such as
0 - propagate unexpected
list-sessionsdiscovery failures as constructor errors instead of silently treating them as "no sessions" - clean up the bootstrap session with a best-effort
kill-sessionwhen theTmuxhandle is closed
tests
- added unit coverage for existing-session startup
- added unit coverage for empty-server bootstrap startup
- added unit coverage for discovery error propagation
- added unit coverage for bootstrap-session cleanup on
Close()
caveat
if a caller keeps a Tmux handle open while the server still has no real user sessions, the named bootstrap session may remain visible until Close() is called. this avoids stealing the first numeric session name and keeps the control-mode client alive, but it is not yet an immediate auto-retirement of the bootstrap session.
commit
9111a04fix: avoid phantom tmux sessions on empty servers
v0.1.0
target-string APIs and missing surfaces
this release adds target-string methods and option struct extensions so
consumers can create and manipulate sessions, windows, and panes by tmux target
string (e.g. "mysession:2", "mysession:2.1") without needing materialized
Go objects. this is particularly useful for bulk session restore workflows.
new methods
Tmux.SplitWindow(target, opts)— split a pane by target string,
with optional direction, start directory, detached mode, and startup commandTmux.SelectLayout(target, layout)— apply a layout string (including
custom checksum layouts) to a window by target stringTmux.GlobalOption(key)— query server-level global options via
show-option -gqv; returns empty string for unset options
extended option structs
NewWindowOptions— addedIndex *int(target a specific window index)
andShellCommand string(startup command as last positional arg)SplitWindowOptions— addedDetached bool(-dflag to keep focus on
current pane)
notes
Tmux.SelectPane(target)andTmux.SelectWindow(target)were already
available since v0.0.1SessionOptions.ShellCommandalready supported startup commands for
new-sessionsince v0.0.1- no breaking changes — all existing APIs are unchanged
commits
e429228feat: add Index and ShellCommand fields to NewWindowOptions7a1ce5ffeat: add Detached to SplitWindowOptions; add Tmux.SplitWindow()ffbb3c9feat: add Tmux.GlobalOption() for server-level option queries0738282feat: add Tmux.SelectLayout() for target-based layout selection