Skip to content

Releases: atomicstack/gotmuxcc

v0.4.0

Choose a tag to compare

@atomicstack atomicstack released this 13 Sep 02:22

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 -A and -r arguments 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), so refresh-client -A %0:on was a hard parse error and SetPaneOutput, EnablePaneOutput, DisablePaneOutput, PausePaneOutput, ContinuePaneOutput, SetMultiplePaneOutputs and ReportPaneColors had 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 %0 targets 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/%end pair carrying a flags field of 0; pairing that with pending[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-change is 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 via break-pane -W (71034b7).
  • the control client now attaches by #{session_id} rather than #{session_name}. tmux allows : and . in names again since 166267c8, and attach-session -t reads those as the session:window.pane separators, so NewTmux returned a client whose transport was already dead and the first call failed with "tmux exit" (1cce29e).
  • Options passes show-options -H, restoring user options registered as hooks that tmux 7277712c began 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, WindowLayoutMainHorizontalMirror and WindowLayoutMainVerticalMirror (8ca39b0).
  • pane and window flag characters are documented including tmux 3.8's A (float above zoom) and O (modal pane), and SetControlFlags documents new-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 … 0 guard fixtures across 14 files claimed tmux sends 0 for replies to commands the client had typed, which real tmux never does; they are corrected to 1. handshake and initial-attach fixtures, which genuinely carry 0, are unchanged (f9910fb).

caveats

  • WindowLayoutMainVertical deliberately keeps its inherited value of main-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; use WindowLayoutMainVerticalLayout for main-vertical (8ca39b0).
  • the show-options -H fallback for servers predating the flag is covered by unit tests only; it was not exercised against a real pre-3.8 server (ce54bb1).
  • quoteArgument now quotes arguments such as %0:on that 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.0

v0.3.0

Choose a tag to compare

@atomicstack atomicstack released this 12 Sep 15:25

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, DisplayMessageContext and SetControlFlagsContext; the existing methods retain their signatures and delegate through context.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 make Close() unblock concurrent Send() 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.0

v0.2.0

Choose a tag to compare

@atomicstack atomicstack released this 23 Aug 04:31

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/%exit as protocol frames even inside an open block, so capture-pane -p output could be parsed as protocol. a captured line starting %exit tore down the entire connection; a captured %begin mis-bound the next queued request; a captured %end with 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 in regress/control-notify-guard.sh after 6db5175e (a6f21f3)
  • guards are now matched as whole keywords, so %beginning, %errors and %exited are notifications again rather than malformed frames, and parseFrame requires exactly three decimal fields (a6f21f3)
  • %end/%error are matched on the full (time, number, flags) triple rather than the command number alone, and %exit is only honoured at depth 0 — tmux prints it from the client process after proc_loop returns, so inside a block it is always pane content (a6f21f3)
  • Tmux.Close() wrote router, transport and Socket with no synchronisation while runCommand read router, 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 via failAll, so clearing them bought nothing but the race. Close is now idempotent and safe to call alongside in-flight commands (11bfb6d)
  • fixed a send on closed channel panic 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, ListPanesFormatContext and CommandContext. existing signatures are unchanged and delegate through context.Background(), so this is purely additive. the context reaches every command in the ListAllWindows/ListAllPanes fallback chains, not just the first (cbf3afb)
  • DefaultHandshakeTimeout (10s) and WithHandshakeTimeout bound the initial control-mode handshake, so a tmux that neither completes the handshake nor closes the transport can no longer wedge NewTmux/DefaultTmux forever. 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-panes output: Pane.FloatingFlag, ModalFlag, Z, Flags, X, Y, UnzoomedWidth, UnzoomedHeight, and Window.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 %begin with pending[0]
  • the go directive 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)
  • GetClientByTty was the only lookup returning its error unwrapped; it now wraps like the other six, so its error string has changed (the wrap uses %w, so errors.Is/As are 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)
  • Close driven concurrently with a command loop, failing under -race on 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.0

v0.1.4

Choose a tag to compare

@atomicstack atomicstack released this 13 Jul 23:39

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 sets Pdeathsig: SIGKILL (the kernel reaps the child on parent death) plus Setpgid; darwin/bsd set Setpgid only, since there is no pdeathsig equivalent; non-unix returns nil (440c9d0)
  • derive an internal cancelable lifetime context in New, cancelled by Close() and on child exit, giving a second CommandContext-based kill path independent of Process.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 (Setpgid everywhere, Pdeathsig on linux)
  • that New wires SysProcAttr onto the command
  • that NewTmuxContext threads its context through to the dialer

upgrading

go get github.com/atomicstack/gotmuxcc@v0.1.4

v0.1.3

Choose a tag to compare

@atomicstack atomicstack released this 13 Jul 23:39

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 Session helpers, using an id-first target helper that falls back to Name for manually constructed Session values without an Id (38536dc)
  • refresh Session.Name after a successful Rename so 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 / claude2 name collision, verifying only the intended session is removed

upgrading

go get github.com/atomicstack/gotmuxcc@v0.1.3

v0.1.2

Choose a tag to compare

@atomicstack atomicstack released this 27 Mar 01:03

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.2

v0.1.1

Choose a tag to compare

@atomicstack atomicstack released this 20 Mar 21:42

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-sessions discovery failures as constructor errors instead of silently treating them as "no sessions"
  • clean up the bootstrap session with a best-effort kill-session when the Tmux handle 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

  • 9111a04 fix: avoid phantom tmux sessions on empty servers

v0.1.0

Choose a tag to compare

@atomicstack atomicstack released this 20 Mar 18:27

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 command
  • Tmux.SelectLayout(target, layout) — apply a layout string (including
    custom checksum layouts) to a window by target string
  • Tmux.GlobalOption(key) — query server-level global options via
    show-option -gqv; returns empty string for unset options

extended option structs

  • NewWindowOptions — added Index *int (target a specific window index)
    and ShellCommand string (startup command as last positional arg)
  • SplitWindowOptions — added Detached bool (-d flag to keep focus on
    current pane)

notes

  • Tmux.SelectPane(target) and Tmux.SelectWindow(target) were already
    available since v0.0.1
  • SessionOptions.ShellCommand already supported startup commands for
    new-session since v0.0.1
  • no breaking changes — all existing APIs are unchanged

commits

  • e429228 feat: add Index and ShellCommand fields to NewWindowOptions
  • 7a1ce5f feat: add Detached to SplitWindowOptions; add Tmux.SplitWindow()
  • ffbb3c9 feat: add Tmux.GlobalOption() for server-level option queries
  • 0738282 feat: add Tmux.SelectLayout() for target-based layout selection