Repository navigation
v0.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.0