Repository navigation
Investigate libtmux-side trim detection for scrollback consumers #671
Description
Problem. libtmux consumers that anchor on display-message + capture-pane for "wait for new output" semantics — e.g. libtmux-mcp's wait_for_text — cannot reliably detect that grid_collect_history trim has fired during continuous output. tmux trims (~10% of history-limit) then immediately scrolls new lines back, so sampled #{history_size} stays clamped near the cap and never appears below the anchor value. A 2 ms high-frequency probe across ~3000 samples confirmed there is no sub-poll window where hsize dips below entry once entry was sub-cap.
Verified gap in tmux's exposed surface (referenced at tmux@134ba6c):
grid.cgrid_collect_history— decrementsgd->hsizebyhlimit/10when at cap; rows are freed immediatelygrid-view.cgrid_view_scroll_region_up— calls collect then scroll in the same call, so the dip is sub-tickcmd-capture-pane.ccmd_capture_pane_history—-S npositive evaluates against livehsizeat capture time, so absolute-index math drifts after a trimformat.c— only exposes#{history_size},#{history_bytes},#{history_limit},#{history_all_bytes}; none monotonic across trimsoptions-table.c— 68 hooks total; none fire on history trim (nohistory-trimmed, nogrid-collected)grid_linecarries atimefield but it isn't exposed via format strings
Investigation directions for libtmux:
-
Wrap a delta-detection helper. A
Pane.observe_grid(callable)primitive that captures row-0 content +(hsize, cursor_y)at entry, polls the same per tick, and returns "an eviction definitely happened since entry" when row-0 content changes. This is what every wait-style consumer is reinventing. -
Per-line content sentinel option. Capture row 0 contents at entry; compare each poll; if the row-0 bytes differ, trim happened (or
clear-historyran). Costs one extracapture-pane -S cy0 -E cy0per tick. -
Upstream feature request to tmux. A monotonic
#{history_lines_evicted}format string, or ahistory-trimmedhook, would let consumers detect the event without polling row-0 content. This is the cleanest fix but needs upstream agreement. -
Document the limitation in
Pane.capture_panedocs so downstream consumers know the limit before they roll their own polling layer.
Why this lands here, not on tmux/tmux directly. libtmux is the consumer-facing API; tmux's grid model is what it is. If libtmux can offer a shared helper, every downstream tool (libtmux-mcp, tmuxinator-style harnesses, test scaffolding, agent flows) benefits at once. An upstream tmux feature would still want a libtmux wrapper.
Downstream context. libtmux-mcp ships wait_for_text with an honest "best-effort near history-limit" contract plus a runtime ctx.warning in the trim-risk band, and steers agents to wait_for_channel (composed tmux wait-for -S) for deterministic command completion. The PR landing that work documents this same gap. A libtmux-level helper would let multiple downstream tools share the cost.
Activity