perf(state): batch tmux metadata in collect_state — ~13× faster /api/state - #118
Merged
Conversation
…rocesses)
/api/state's collect_state iterated libtmux objects — srv.sessions,
s.windows, w.active_pane, pane.pane_current_command/path — and libtmux
forks a separate `tmux` subprocess for each lazy property access
(~150ms per window). At the 500ms modal-open poll cadence this spawned
dozens of tmux subprocesses per second, flooding the tmux server so the
user's keystrokes (each a `tmux send-keys`) queued behind it — laggy
typing and slow modal opens that scaled with pane count and were
amplified under system memory pressure.
Replace the per-window/per-pane libtmux fan-out with two batched
commands — one `tmux list-panes -a -f '#{pane_active}' -F <fmt>` for all
active-pane metadata and one `list-clients -F <fmt>` — parsed by pure
helpers (_parse_pane_rows / _parse_clients_grouped). Sessions and windows
are derived from the rows; capture-pane stays per-pane (already cached,
THI-181, now keyed by session:window target).
Measured live (6 panes): /api/state median 380ms → 29ms, max 810ms →
84ms (~13x); eliminates the multi-second spikes seen under load. Response
schema is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the real cause of the "app gets laggy over time" reports:
/api/statewas slow, and the dashboard polls it every 500 ms while a terminal modal is open.collect_state()iterated libtmux objects —srv.sessions,s.windows,w.active_pane,pane.pane_current_command,pane.pane_current_path— and libtmux forks a separatetmuxsubprocess for each lazy property access (~150 ms per window). At the modal-open cadence that spawned dozens of tmux subprocesses per second, flooding the tmux server so the user's keystrokes — each atmux send-keys— queued behind the storm. Result: laggy typing + slow modal opens that scaled with pane count and got dramatically worse under system memory pressure (subprocess forks stall on paging → multi-second/api/statespikes).Change
Replace the per-window/per-pane libtmux fan-out with two batched tmux commands:
tmux list-panes -a -f '#{pane_active}' -F <fmt>— all active-pane metadata (session + window + pane) in one subprocess.tmux list-clients -F <fmt>— all clients, grouped onto their session.Parsed by pure, unit-tested helpers (
_parse_pane_rows,_parse_clients_grouped); sessions/windows are derived from the rows.capture-panestays per-pane (already cached, THI-181 — now keyed bysession:windowtarget). TheStateResponseschema is unchanged, so the frontend is untouched.Measured (live, 6 panes)
~13× faster, and the multi-second spikes are gone. The win grows with pane count (old cost was linear in windows; new cost is ~flat).
Tests (TDD)
_parse_pane_rows,_parse_clients_grouped): delimited-field parsing, blank/short-row skipping, spaces in names/paths, client grouping. Watched them fail (helpers didn't exist), then pass.collect_statetests to drive the new batchedsrv.cmddata source — covering sb-usage filtering, one-card-per-session dedup, capture-pane TTL cache (hit within TTL / re-capture after), client population/attached, malformed-row skip, and graceful degradation whenlist-paneserrors mid-scan (no 500).Notes
🤖 Generated with Claude Code